diff --git a/TelegramUI.xcodeproj/project.pbxproj b/TelegramUI.xcodeproj/project.pbxproj index c321e24a4c..3f15f220a3 100644 --- a/TelegramUI.xcodeproj/project.pbxproj +++ b/TelegramUI.xcodeproj/project.pbxproj @@ -7,6 +7,10 @@ objects = { /* Begin PBXBuildFile section */ + 091346962183496900846E49 /* InstantPageArticleItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 091346952183496900846E49 /* InstantPageArticleItem.swift */; }; + 091346982183498A00846E49 /* InstantPageArticleNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 091346972183498A00846E49 /* InstantPageArticleNode.swift */; }; + 0913469A218528D200846E49 /* InstantPageTableItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09134699218528D200846E49 /* InstantPageTableItem.swift */; }; + 0913469C21883C3700846E49 /* InstantPageDetailsItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0913469B21883C3700846E49 /* InstantPageDetailsItem.swift */; }; 091BEAB3214552D9003AEA30 /* Vision.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D02DADBE2138D76F00116225 /* Vision.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 092F368D2154AAEA001A9F49 /* SFCompactRounded-Semibold.otf in Resources */ = {isa = PBXBuildFile; fileRef = 092F368C2154AAE9001A9F49 /* SFCompactRounded-Semibold.otf */; }; 092F36902157AB46001A9F49 /* ItemListCallListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 092F368F2157AB46001A9F49 /* ItemListCallListItem.swift */; }; @@ -24,6 +28,9 @@ 0941A9A6210B822D00EBE194 /* OpenInOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0941A9A5210B822D00EBE194 /* OpenInOptions.swift */; }; 0952D1752176DEB500194860 /* NotificationMuteSettingsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0952D1742176DEB500194860 /* NotificationMuteSettingsController.swift */; }; 0952D1772177FB5400194860 /* WatchPresetSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0952D1762177FB5400194860 /* WatchPresetSettings.swift */; }; + 0958FBB9218AD6AF00E0CBD8 /* InstantPageFeedbackItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0958FBB8218AD6AF00E0CBD8 /* InstantPageFeedbackItem.swift */; }; + 0958FBBB218AD6BC00E0CBD8 /* InstantPageFeedbackNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0958FBBA218AD6BC00E0CBD8 /* InstantPageFeedbackNode.swift */; }; + 0958FBBD218B03CA00E0CBD8 /* InstantPageDetailsNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0958FBBC218B03CA00E0CBD8 /* InstantPageDetailsNode.swift */; }; 096C98BA21787A5C00C211FF /* LegacyBridgeAudio.swift in Sources */ = {isa = PBXBuildFile; fileRef = 096C98B921787A5C00C211FF /* LegacyBridgeAudio.swift */; }; 096C98BF21787C6700C211FF /* TGBridgeAudioEncoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 096C98BB21787C6600C211FF /* TGBridgeAudioEncoder.m */; }; 096C98C021787C6700C211FF /* TGBridgeAudioEncoder.h in Headers */ = {isa = PBXBuildFile; fileRef = 096C98BC21787C6600C211FF /* TGBridgeAudioEncoder.h */; }; @@ -1041,6 +1048,10 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 091346952183496900846E49 /* InstantPageArticleItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstantPageArticleItem.swift; sourceTree = ""; }; + 091346972183498A00846E49 /* InstantPageArticleNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstantPageArticleNode.swift; sourceTree = ""; }; + 09134699218528D200846E49 /* InstantPageTableItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstantPageTableItem.swift; sourceTree = ""; }; + 0913469B21883C3700846E49 /* InstantPageDetailsItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstantPageDetailsItem.swift; sourceTree = ""; }; 092F368C2154AAE9001A9F49 /* SFCompactRounded-Semibold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SFCompactRounded-Semibold.otf"; sourceTree = ""; }; 092F368F2157AB46001A9F49 /* ItemListCallListItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemListCallListItem.swift; sourceTree = ""; }; 09310D14213BC5DE0020033A /* anim_read.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = anim_read.json; sourceTree = ""; }; @@ -1057,6 +1068,9 @@ 0941A9A5210B822D00EBE194 /* OpenInOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenInOptions.swift; sourceTree = ""; }; 0952D1742176DEB500194860 /* NotificationMuteSettingsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationMuteSettingsController.swift; sourceTree = ""; }; 0952D1762177FB5400194860 /* WatchPresetSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WatchPresetSettings.swift; sourceTree = ""; }; + 0958FBB8218AD6AF00E0CBD8 /* InstantPageFeedbackItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstantPageFeedbackItem.swift; sourceTree = ""; }; + 0958FBBA218AD6BC00E0CBD8 /* InstantPageFeedbackNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstantPageFeedbackNode.swift; sourceTree = ""; }; + 0958FBBC218B03CA00E0CBD8 /* InstantPageDetailsNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstantPageDetailsNode.swift; sourceTree = ""; }; 096C98B921787A5C00C211FF /* LegacyBridgeAudio.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyBridgeAudio.swift; sourceTree = ""; }; 096C98BB21787C6600C211FF /* TGBridgeAudioEncoder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TGBridgeAudioEncoder.m; sourceTree = ""; }; 096C98BC21787C6600C211FF /* TGBridgeAudioEncoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TGBridgeAudioEncoder.h; sourceTree = ""; }; @@ -3024,6 +3038,13 @@ D048EA8A1F4F298A00188713 /* InstantPageSettingsThemeItemNode.swift */, D048EA8C1F4F299A00188713 /* InstantPageSettingsSwitchItemNode.swift */, 09797872210633CD0077D77F /* InstantPageSettingsButtonItemNode.swift */, + 09134699218528D200846E49 /* InstantPageTableItem.swift */, + 091346952183496900846E49 /* InstantPageArticleItem.swift */, + 091346972183498A00846E49 /* InstantPageArticleNode.swift */, + 0913469B21883C3700846E49 /* InstantPageDetailsItem.swift */, + 0958FBBC218B03CA00E0CBD8 /* InstantPageDetailsNode.swift */, + 0958FBB8218AD6AF00E0CBD8 /* InstantPageFeedbackItem.swift */, + 0958FBBA218AD6BC00E0CBD8 /* InstantPageFeedbackNode.swift */, ); name = "Instant Page"; sourceTree = ""; @@ -4827,6 +4848,7 @@ D0E412DF206AA00500BEE4A2 /* SecureIdVerificationDocumentsContext.swift in Sources */, D0EC6CCC1EB9F58800EBF1C3 /* ServiceSoundManager.swift in Sources */, D0EC6CCD1EB9F58800EBF1C3 /* DeclareEncodables.swift in Sources */, + 0958FBBD218B03CA00E0CBD8 /* InstantPageDetailsNode.swift in Sources */, D02D60B1206C189900FEFE1E /* SecureIdPlaintextFormController.swift in Sources */, D0CFBB951FD8B05000B65C0D /* OverlayInstantVideoDecoration.swift in Sources */, D0EC6CCE1EB9F58800EBF1C3 /* TelegramApplicationContext.swift in Sources */, @@ -4838,6 +4860,7 @@ D0FC4FBB1F751E8900B7443F /* SelectablePeerNode.swift in Sources */, D0E9BAD21F0573C000F079A4 /* STPToken.m in Sources */, D0EC6CD31EB9F58800EBF1C3 /* GenerateTextEntities.swift in Sources */, + 0913469A218528D200846E49 /* InstantPageTableItem.swift in Sources */, D0EC6CD41EB9F58800EBF1C3 /* StringWithAppliedEntities.swift in Sources */, D0192D46210F4F950005FA10 /* FixSearchableListNodeScrolling.swift in Sources */, D0EC6CD51EB9F58800EBF1C3 /* StoredMessageFromSearchPeer.swift in Sources */, @@ -4993,6 +5016,7 @@ D0EC6D261EB9F58800EBF1C3 /* TransformOutgoingMessageMedia.swift in Sources */, D0EC6D271EB9F58800EBF1C3 /* FetchResource.swift in Sources */, D048EA8F1F4F2A9C00188713 /* InstantPageSettingsItemNode.swift in Sources */, + 0913469C21883C3700846E49 /* InstantPageDetailsItem.swift in Sources */, D056CD721FF1569800880D28 /* MusicPlaybackSettings.swift in Sources */, D0A723541FC3B40E0094D167 /* RadialCheckContentNode.swift in Sources */, D09D88731F86D56B00BEB4C9 /* AuthorizationLayout.swift in Sources */, @@ -5229,6 +5253,7 @@ 09797873210633CD0077D77F /* InstantPageSettingsButtonItemNode.swift in Sources */, D0EC6D9F1EB9F58900EBF1C3 /* ChatUnreadItem.swift in Sources */, D0E9B9E81EFEFB9500F079A4 /* BotPaymentDisclosureItemNode.swift in Sources */, + 091346962183496900846E49 /* InstantPageArticleItem.swift in Sources */, D0EC6DA01EB9F58900EBF1C3 /* ChatHoleItem.swift in Sources */, D093D82020699A7300BC3599 /* FormController.swift in Sources */, D0EC6DA11EB9F58900EBF1C3 /* ChatMessageSelectionNode.swift in Sources */, @@ -5290,6 +5315,7 @@ D0EC6DC11EB9F58900EBF1C3 /* ChatMediaInputTrendingItem.swift in Sources */, D0BCC3D620404CD8008126C2 /* ChatMessageActionSheetControllerNode.swift in Sources */, D0EC6DC21EB9F58900EBF1C3 /* ChatMediaInputStickerPackItem.swift in Sources */, + 091346982183498A00846E49 /* InstantPageArticleNode.swift in Sources */, D0EC6DC31EB9F58900EBF1C3 /* ChatMediaInputStickerGridItem.swift in Sources */, D0EC6DC41EB9F58900EBF1C3 /* MultiplexedSoftwareVideoNode.swift in Sources */, D0BE30432061B80B00FBE6D8 /* SecureIdAuthControllerNode.swift in Sources */, @@ -5475,6 +5501,7 @@ D0EC6E2D1EB9F58900EBF1C3 /* CounterContollerTitleView.swift in Sources */, D0AEAE292080FD660013176E /* StickerPaneSearchGlobaltem.swift in Sources */, D0F19F6220E5694D00EEC860 /* GroupStickerPackCurrentItem.swift in Sources */, + 0958FBBB218AD6BC00E0CBD8 /* InstantPageFeedbackNode.swift in Sources */, D0EC6E2E1EB9F58900EBF1C3 /* ContactMultiselectionController.swift in Sources */, D0EC6E2F1EB9F58900EBF1C3 /* ContactMultiselectionControllerNode.swift in Sources */, D0EC6E301EB9F58900EBF1C3 /* ContactSelectionController.swift in Sources */, @@ -5505,6 +5532,7 @@ D0EC6E3E1EB9F58900EBF1C3 /* ItemListSectionHeaderItem.swift in Sources */, D0EC6E3F1EB9F58900EBF1C3 /* ItemListTextItem.swift in Sources */, D0EC6E401EB9F58900EBF1C3 /* ItemListActivityTextItem.swift in Sources */, + 0958FBB9218AD6AF00E0CBD8 /* InstantPageFeedbackItem.swift in Sources */, D0EC6E411EB9F58900EBF1C3 /* ItemListEditableItem.swift in Sources */, D0EC6E421EB9F58900EBF1C3 /* ItemListRevealOptionsNode.swift in Sources */, D0E8175920122FE100B82BBB /* ChatRecentActionsFilterController.swift in Sources */, diff --git a/TelegramUI/BotCheckoutWebInteractionControllerNode.swift b/TelegramUI/BotCheckoutWebInteractionControllerNode.swift index 7387993ee3..d66e3fff30 100644 --- a/TelegramUI/BotCheckoutWebInteractionControllerNode.swift +++ b/TelegramUI/BotCheckoutWebInteractionControllerNode.swift @@ -65,6 +65,9 @@ final class BotCheckoutWebInteractionControllerNode: ViewControllerTracingNode, } webView.navigationDelegate = self } + if #available(iOSApplicationExtension 11.0, *) { + webView.scrollView.contentInsetAdjustmentBehavior = .never + } self.webView = webView self.view.addSubview(webView) diff --git a/TelegramUI/CallListController.swift b/TelegramUI/CallListController.swift index ef7d3048e1..11abbc3e26 100644 --- a/TelegramUI/CallListController.swift +++ b/TelegramUI/CallListController.swift @@ -49,13 +49,7 @@ public final class CallListController: ViewController { if case .tab = self.mode { self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationCallIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.callPressed)) - let icon: UIImage? - if (useSpecialTabBarIcons()) { - icon = UIImage(bundleImageName: "Chat List/Tabs/IconCallsHW") - } else { - icon = UIImage(bundleImageName: "Chat List/Tabs/IconCalls") - } - + let icon = UIImage(bundleImageName: "Chat List/Tabs/IconCalls") self.tabBarItem.title = self.presentationData.strings.Calls_TabTitle self.tabBarItem.image = icon self.tabBarItem.selectedImage = icon diff --git a/TelegramUI/ChatImageGalleryItem.swift b/TelegramUI/ChatImageGalleryItem.swift index 17a9521b6f..771f8d1d0c 100644 --- a/TelegramUI/ChatImageGalleryItem.swift +++ b/TelegramUI/ChatImageGalleryItem.swift @@ -73,16 +73,20 @@ class ChatImageGalleryItem: GalleryItem { let presentationData: PresentationData let message: Message let location: MessageHistoryEntryLocation? + let openUrl: (String) -> Void + let openUrlOptions: (String) -> Void - init(account: Account, presentationData: PresentationData, message: Message, location: MessageHistoryEntryLocation?) { + init(account: Account, presentationData: PresentationData, message: Message, location: MessageHistoryEntryLocation?, openUrl: @escaping (String) -> Void, openUrlOptions: @escaping (String) -> Void) { self.account = account self.presentationData = presentationData self.message = message self.location = location + self.openUrl = openUrl + self.openUrlOptions = openUrlOptions } func node() -> GalleryItemNode { - let node = ChatImageGalleryItemNode(account: self.account, presentationData: self.presentationData) + let node = ChatImageGalleryItemNode(account: self.account, presentationData: self.presentationData, openUrl: self.openUrl, openUrlOptions: self.openUrlOptions) for media in self.message.media { if let image = media as? TelegramMediaImage { @@ -156,11 +160,13 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode { private let statusDisposable = MetaDisposable() private var status: MediaResourceStatus? - init(account: Account, presentationData: PresentationData) { + init(account: Account, presentationData: PresentationData, openUrl: @escaping (String) -> Void, openUrlOptions: @escaping (String) -> Void) { self.account = account self.imageNode = TransformImageNode() self.footerContentNode = ChatItemGalleryFooterContentNode(account: account, presentationData: presentationData) + self.footerContentNode.openUrl = openUrl + self.footerContentNode.openUrlOptions = openUrlOptions self.statusNodeContainer = HighlightableButtonNode() self.statusNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.5)) diff --git a/TelegramUI/ChatItemGalleryFooterContentNode.swift b/TelegramUI/ChatItemGalleryFooterContentNode.swift index 2e4166d627..a172296ea4 100644 --- a/TelegramUI/ChatItemGalleryFooterContentNode.swift +++ b/TelegramUI/ChatItemGalleryFooterContentNode.swift @@ -55,7 +55,6 @@ private let playImage = generateImage(CGSize(width: 15.0, height: 18.0), rotated context.translateBy(x: -(diameter - size.width) / 2.0 - 1.5, y: -(diameter - size.height) / 2.0) }) -private let textFont = Font.regular(16.0) private let titleFont = Font.medium(15.0) private let dateFont = Font.regular(14.0) @@ -79,7 +78,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode { private let forwardButton: HighlightableButtonNode private let playbackControlButton: HighlightableButtonNode - private var currentMessageText: String? + private var currentMessageText: NSAttributedString? private var currentAuthorNameText: String? private var currentDateText: String? @@ -91,6 +90,9 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode { var seekBackward: (() -> Void)? var seekForward: (() -> Void)? + var openUrl: ((String) -> Void)? + var openUrlOptions: ((String) -> Void)? + var content: ChatItemGalleryFooterContent = .info { didSet { //if self.content != oldValue { @@ -140,7 +142,8 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode { self.textNode = ImmediateTextNode() self.textNode.maximumNumberOfLines = 10 - self.textNode.isLayerBacked = true + self.textNode.linkHighlightColor = UIColor(white: 1.0, alpha: 0.4) + self.authorNameNode = ASTextNode() self.authorNameNode.maximumNumberOfLines = 1 self.authorNameNode.isLayerBacked = true @@ -163,6 +166,24 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode { super.init() + self.textNode.highlightAttributeAction = { attributes in + if let _ = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.URL)] { + return NSAttributedStringKey(rawValue: TelegramTextAttributes.URL) + } else { + return nil + } + } + self.textNode.tapAttributeAction = { [weak self] attributes in + if let strongSelf = self, let url = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.URL)] as? String { + strongSelf.openUrl?(url) + } + } + self.textNode.longTapAttributeAction = { [weak self] attributes in + if let strongSelf = self, let url = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.URL)] as? String { + strongSelf.openUrlOptions?(url) + } + } + self.view.addSubview(self.deleteButton) self.view.addSubview(self.actionButton) self.addSubnode(self.textNode) @@ -185,19 +206,19 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode { self.messageContextDisposable.dispose() } - func setup(origin: GalleryItemOriginData?, caption: String) { + func setup(origin: GalleryItemOriginData?, caption: NSAttributedString) { let titleText = origin?.title let dateText = origin?.timestamp.flatMap { humanReadableStringForTimestamp(strings: self.strings, dateTimeFormat: self.dateTimeFormat, timestamp: $0) } if self.currentMessageText != caption || self.currentAuthorNameText != titleText || self.currentDateText != dateText { self.currentMessageText = caption - if caption.isEmpty { + if caption.length == 0 { self.textNode.isHidden = true self.textNode.attributedText = nil } else { self.textNode.isHidden = false - self.textNode.attributedText = NSAttributedString(string: caption, font: textFont, textColor: .white) + self.textNode.attributedText = caption } if let titleText = titleText { @@ -251,7 +272,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode { let dateText = humanReadableStringForTimestamp(strings: self.strings, dateTimeFormat: self.dateTimeFormat, timestamp: message.timestamp) - var messageText = "" + var messageText = NSAttributedString(string: "") var hasCaption = false for media in message.media { if media is TelegramMediaImage { @@ -261,18 +282,25 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode { } } if hasCaption { - messageText = message.text + var entities: [MessageTextEntity] = [] + for attribute in message.attributes { + if let attribute = attribute as? TextEntitiesMessageAttribute { + entities = attribute.entities + break + } + } + messageText = galleryCaptionStringWithAppliedEntities(message.text, entities: entities) } if self.currentMessageText != messageText || canDelete != !self.deleteButton.isHidden || self.currentAuthorNameText != authorNameText || self.currentDateText != dateText { self.currentMessageText = messageText - if messageText.isEmpty { + if messageText.length == 0 { self.textNode.isHidden = true self.textNode.attributedText = nil } else { self.textNode.isHidden = false - self.textNode.attributedText = NSAttributedString(string: messageText, font: textFont, textColor: .white) + self.textNode.attributedText = messageText } if let authorNameText = authorNameText { diff --git a/TelegramUI/ChatListController.swift b/TelegramUI/ChatListController.swift index 854d06dd63..51c16d0b2c 100644 --- a/TelegramUI/ChatListController.swift +++ b/TelegramUI/ChatListController.swift @@ -26,13 +26,6 @@ import TelegramCore // }) //}) -public func useSpecialTabBarIcons() -> Bool { - let calendar = Calendar(identifier: .gregorian) - let now = calendar.dateComponents([.year, .month, .day], from: Date()) - let target = calendar.dateComponents([.year, .month, .day], from: Date(timeIntervalSince1970: 1540987200)) - return now.day == target.day && now.month == target.month && now.year == target.year -} - public class ChatListController: TelegramController, KeyShortcutResponder, UIViewControllerPreviewingDelegate { private var validLayout: ContainerViewLayout? @@ -86,13 +79,7 @@ public class ChatListController: TelegramController, KeyShortcutResponder, UIVie self.navigationItem.titleView = self.titleView self.tabBarItem.title = self.presentationData.strings.DialogList_Title - let icon: UIImage? - if (useSpecialTabBarIcons()) { - icon = UIImage(bundleImageName: "Chat List/Tabs/IconChatsHW") - } else { - icon = UIImage(bundleImageName: "Chat List/Tabs/IconChats") - } - + let icon = UIImage(bundleImageName: "Chat List/Tabs/IconChats") self.tabBarItem.image = icon self.tabBarItem.selectedImage = icon diff --git a/TelegramUI/ChatMessageBubbleItemNode.swift b/TelegramUI/ChatMessageBubbleItemNode.swift index 27569668a6..238521df33 100644 --- a/TelegramUI/ChatMessageBubbleItemNode.swift +++ b/TelegramUI/ChatMessageBubbleItemNode.swift @@ -1916,7 +1916,8 @@ class ChatMessageBubbleItemNode: ChatMessageItemView { } self.item?.controllerInteraction.cancelInteractiveKeyboardGestures() case .changed: - let translation = recognizer.translation(in: self.view) + var translation = recognizer.translation(in: self.view) + translation.x = max(-80.0, min(0.0, translation.x)) var animateReplyNodeIn = false if (translation.x < -45.0) != (self.currentSwipeToReplyTranslation < -45.0) { if translation.x < -45.0, self.swipeToReplyNode == nil, let item = self.item { @@ -1938,6 +1939,8 @@ class ChatMessageBubbleItemNode: ChatMessageItemView { if animateReplyNodeIn { swipeToReplyNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.12) swipeToReplyNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4) + } else { + swipeToReplyNode.alpha = min(1.0, abs(translation.x / 45.0)) } } case .cancelled, .ended: diff --git a/TelegramUI/ChatMessageInstantVideoItemNode.swift b/TelegramUI/ChatMessageInstantVideoItemNode.swift index 3347cb0f43..123a750fdf 100644 --- a/TelegramUI/ChatMessageInstantVideoItemNode.swift +++ b/TelegramUI/ChatMessageInstantVideoItemNode.swift @@ -380,7 +380,8 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { } (self.view.window as? WindowHost)?.cancelInteractiveKeyboardGestures() case .changed: - let translation = recognizer.translation(in: self.view) + var translation = recognizer.translation(in: self.view) + translation.x = max(-80.0, min(0.0, translation.x)) var animateReplyNodeIn = false if (translation.x < -45.0) != (self.currentSwipeToReplyTranslation < -45.0) { if translation.x < -45.0, self.swipeToReplyNode == nil, let item = self.item { @@ -402,6 +403,8 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { if animateReplyNodeIn { swipeToReplyNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.12) swipeToReplyNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4) + } else { + swipeToReplyNode.alpha = min(1.0, abs(translation.x / 45.0)) } } case .cancelled, .ended: diff --git a/TelegramUI/ChatMessageStickerItemNode.swift b/TelegramUI/ChatMessageStickerItemNode.swift index 0e6319e47e..4281a0ebd8 100644 --- a/TelegramUI/ChatMessageStickerItemNode.swift +++ b/TelegramUI/ChatMessageStickerItemNode.swift @@ -397,7 +397,8 @@ class ChatMessageStickerItemNode: ChatMessageItemView { } (self.view.window as? WindowHost)?.cancelInteractiveKeyboardGestures() case .changed: - let translation = recognizer.translation(in: self.view) + var translation = recognizer.translation(in: self.view) + translation.x = max(-80.0, min(0.0, translation.x)) var animateReplyNodeIn = false if (translation.x < -45.0) != (self.currentSwipeToReplyTranslation < -45.0) { if translation.x < -45.0, self.swipeToReplyNode == nil, let item = self.item { @@ -419,6 +420,8 @@ class ChatMessageStickerItemNode: ChatMessageItemView { if animateReplyNodeIn { swipeToReplyNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.12) swipeToReplyNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4) + } else { + swipeToReplyNode.alpha = min(1.0, abs(translation.x / 45.0)) } } case .cancelled, .ended: diff --git a/TelegramUI/ChatMessageWebpageBubbleContentNode.swift b/TelegramUI/ChatMessageWebpageBubbleContentNode.swift index 7cab20bd84..2e6b493516 100644 --- a/TelegramUI/ChatMessageWebpageBubbleContentNode.swift +++ b/TelegramUI/ChatMessageWebpageBubbleContentNode.swift @@ -57,7 +57,7 @@ func instantPageGalleryMedia(webpageId: MediaId, page: InstantPage, galleryMedia } if !found { - result.insert(InstantPageGalleryEntry(index: Int32(counter), pageId: webpageId, media: InstantPageMedia(index: counter, media: galleryMedia, caption: ""), caption: "", location: InstantPageGalleryEntryLocation(position: Int32(counter), totalCount: 0)), at: 0) + result.insert(InstantPageGalleryEntry(index: Int32(counter), pageId: webpageId, media: InstantPageMedia(index: counter, media: galleryMedia, caption: nil), caption: nil, location: InstantPageGalleryEntryLocation(position: Int32(counter), totalCount: 0)), at: 0) } for i in 0 ..< result.count { @@ -69,17 +69,15 @@ func instantPageGalleryMedia(webpageId: MediaId, page: InstantPage, galleryMedia private func instantPageBlockMedia(pageId: MediaId, block: InstantPageBlock, media: [MediaId: Media], counter: inout Int) -> [InstantPageGalleryEntry] { switch block { - case let .image(id, caption): + case let .image(id, caption, _, _): if let m = media[id] { - let captionText = caption.plainText - let result = [InstantPageGalleryEntry(index: Int32(counter), pageId: pageId, media: InstantPageMedia(index: counter, media: m, caption: captionText), caption: captionText, location: InstantPageGalleryEntryLocation(position: Int32(counter), totalCount: 0))] + let result = [InstantPageGalleryEntry(index: Int32(counter), pageId: pageId, media: InstantPageMedia(index: counter, media: m, caption: caption.text), caption: caption.text, location: InstantPageGalleryEntryLocation(position: Int32(counter), totalCount: 0))] counter += 1 return result } case let .video(id, caption, _, loop): if let m = media[id] { - let captionText = caption.plainText - let result = [InstantPageGalleryEntry(index: Int32(counter), pageId: pageId, media: InstantPageMedia(index: counter, media: m, caption: captionText), caption: captionText, location: InstantPageGalleryEntryLocation(position: Int32(counter), totalCount: 0))] + let result = [InstantPageGalleryEntry(index: Int32(counter), pageId: pageId, media: InstantPageMedia(index: counter, media: m, caption: caption.text), caption: caption.text, location: InstantPageGalleryEntryLocation(position: Int32(counter), totalCount: 0))] counter += 1 return result } diff --git a/TelegramUI/ContactsController.swift b/TelegramUI/ContactsController.swift index cad551de3a..247b9d5183 100644 --- a/TelegramUI/ContactsController.swift +++ b/TelegramUI/ContactsController.swift @@ -35,13 +35,7 @@ public class ContactsController: ViewController { self.title = self.presentationData.strings.Contacts_Title self.tabBarItem.title = self.presentationData.strings.Contacts_Title - let icon: UIImage? - if (useSpecialTabBarIcons()) { - icon = UIImage(bundleImageName: "Chat List/Tabs/IconContactsHW") - } else { - icon = UIImage(bundleImageName: "Chat List/Tabs/IconContacts") - } - + let icon = UIImage(bundleImageName: "Chat List/Tabs/IconContacts") self.tabBarItem.image = icon self.tabBarItem.selectedImage = icon diff --git a/TelegramUI/GalleryController.swift b/TelegramUI/GalleryController.swift index 3eff758772..48bbdf72b1 100644 --- a/TelegramUI/GalleryController.swift +++ b/TelegramUI/GalleryController.swift @@ -5,6 +5,7 @@ import Postbox import SwiftSignalKit import AsyncDisplayKit import TelegramCore +import SafariServices private func tagsForMessage(_ message: Message) -> MessageTags? { for media in message.media { @@ -121,12 +122,21 @@ func internalDocumentItemSupportsMimeType(_ type: String, fileName: String?) -> return false } -func galleryItemForEntry(account: Account, presentationData: PresentationData, entry: MessageHistoryEntry, streamVideos: Bool, loopVideos: Bool = false, hideControls: Bool = false, playbackCompleted: @escaping () -> Void = {}) -> GalleryItem? { +private let textFont = Font.regular(16.0) +private let boldFont = Font.bold(16.0) +private let italicFont = Font.italic(16.0) +private let fixedFont = UIFont(name: "Menlo-Regular", size: 15.0) ?? textFont + +func galleryCaptionStringWithAppliedEntities(_ text: String, entities: [MessageTextEntity]) -> NSAttributedString { + return stringWithAppliedEntities(text, entities: entities, baseColor: .white, linkColor: .white, baseFont: textFont, linkFont: textFont, boldFont: boldFont, italicFont: italicFont, fixedFont: fixedFont) +} + +func galleryItemForEntry(account: Account, presentationData: PresentationData, entry: MessageHistoryEntry, streamVideos: Bool, loopVideos: Bool = false, hideControls: Bool = false, playbackCompleted: @escaping () -> Void = {}, openUrl: @escaping (String) -> Void = { _ in }, openUrlOptions: @escaping (String) -> Void = { _ in }) -> GalleryItem? { switch entry { case let .MessageEntry(message, _, location, _): if let media = mediaForMessage(message: message) { if let _ = media as? TelegramMediaImage { - return ChatImageGalleryItem(account: account, presentationData: presentationData, message: message, location: location) + return ChatImageGalleryItem(account: account, presentationData: presentationData, message: message, location: location, openUrl: openUrl, openUrlOptions: openUrlOptions) } else if let file = media as? TelegramMediaFile { if file.isVideo || supportedVideoMimeTypes.contains(file.mimeType) { let content: UniversalVideoContent @@ -139,11 +149,20 @@ func galleryItemForEntry(account: Account, presentationData: PresentationData, e content = PlatformVideoContent(id: .message(message.id, message.stableId, file.fileId), fileReference: .message(message: MessageReference(message), media: file), streamVideo: streamVideos, loopVideo: loopVideos) } } - return UniversalVideoGalleryItem(account: account, presentationData: presentationData, content: content, originData: GalleryItemOriginData(title: message.author?.displayTitle, timestamp: message.timestamp), indexData: location.flatMap { GalleryItemIndexData(position: Int32($0.index), totalCount: Int32($0.count)) }, contentInfo: .message(message), caption: message.text, hideControls: hideControls, playbackCompleted: playbackCompleted) + + var entities: [MessageTextEntity] = [] + for attribute in message.attributes { + if let attribute = attribute as? TextEntitiesMessageAttribute { + entities = attribute.entities + break + } + } + let caption = galleryCaptionStringWithAppliedEntities(message.text, entities: entities) + return UniversalVideoGalleryItem(account: account, presentationData: presentationData, content: content, originData: GalleryItemOriginData(title: message.author?.displayTitle, timestamp: message.timestamp), indexData: location.flatMap { GalleryItemIndexData(position: Int32($0.index), totalCount: Int32($0.count)) }, contentInfo: .message(message), caption: caption, hideControls: hideControls, playbackCompleted: playbackCompleted, openUrl: openUrl, openUrlOptions: openUrlOptions) } else { if file.mimeType.hasPrefix("image/") && file.mimeType != "image/gif" { if file.size == nil || file.size! < 5 * 1024 * 1024 { - return ChatImageGalleryItem(account: account, presentationData: presentationData, message: message, location: location) + return ChatImageGalleryItem(account: account, presentationData: presentationData, message: message, location: location, openUrl: openUrl, openUrlOptions: openUrlOptions) } else { return ChatDocumentGalleryItem(account: account, presentationData: presentationData, message: message, location: location) } @@ -156,13 +175,13 @@ func galleryItemForEntry(account: Account, presentationData: PresentationData, e } else if let webpage = media as? TelegramMediaWebpage, case let .Loaded(webpageContent) = webpage.content { switch websiteType(of: webpageContent) { case .instagram where webpageContent.file != nil && webpageContent.image != nil && webpageContent.file!.isVideo: - return UniversalVideoGalleryItem(account: account, presentationData: presentationData, content: NativeVideoContent(id: NativeVideoContentId.message(message.id, message.stableId, webpage.webpageId), fileReference: .message(message: MessageReference(message), media: webpageContent.file!), streamVideo: true, enableSound: true), originData: GalleryItemOriginData(title: message.author?.displayTitle, timestamp: message.timestamp), indexData: location.flatMap { GalleryItemIndexData(position: Int32($0.index), totalCount: Int32($0.count)) }, contentInfo: .message(message), caption: "") + return UniversalVideoGalleryItem(account: account, presentationData: presentationData, content: NativeVideoContent(id: NativeVideoContentId.message(message.id, message.stableId, webpage.webpageId), fileReference: .message(message: MessageReference(message), media: webpageContent.file!), streamVideo: true, enableSound: true), originData: GalleryItemOriginData(title: message.author?.displayTitle, timestamp: message.timestamp), indexData: location.flatMap { GalleryItemIndexData(position: Int32($0.index), totalCount: Int32($0.count)) }, contentInfo: .message(message), caption: NSAttributedString(string: ""), openUrl: openUrl, openUrlOptions: openUrlOptions) //return UniversalVideoGalleryItem(account: account, theme: theme, strings: strings, content: SystemVideoContent(url: webpageContent.embedUrl!, image: webpageContent.image!, dimensions: webpageContent.embedSize ?? CGSize(width: 640.0, height: 640.0), duration: Int32(webpageContent.duration ?? 0)), originData: GalleryItemOriginData(title: message.author?.displayTitle, timestamp: message.timestamp), indexData: location.flatMap { GalleryItemIndexData(position: Int32($0.index), totalCount: Int32($0.count)) }, contentInfo: .message(message), caption: "") /*case .twitter where webpageContent.embedUrl != nil && webpageContent.image != nil: return UniversalVideoGalleryItem(account: account, theme: theme, strings: strings, content: SystemVideoContent(url: webpageContent.embedUrl!, image: webpageContent.image!, dimensions: webpageContent.embedSize ?? CGSize(width: 640.0, height: 640.0), duration: Int32(webpageContent.duration ?? 0)), originData: GalleryItemOriginData(title: message.author?.displayTitle, timestamp: message.timestamp), indexData: location.flatMap { GalleryItemIndexData(position: Int32($0.index), totalCount: Int32($0.count)) }, contentInfo: .message(message), caption: "")*/ default: if let content = WebEmbedVideoContent(webPage: webpage, webpageContent: webpageContent) { - return UniversalVideoGalleryItem(account: account, presentationData: presentationData, content: content, originData: GalleryItemOriginData(title: message.author?.displayTitle, timestamp: message.timestamp), indexData: location.flatMap { GalleryItemIndexData(position: Int32($0.index), totalCount: Int32($0.count)) }, contentInfo: .message(message), caption: "") + return UniversalVideoGalleryItem(account: account, presentationData: presentationData, content: content, originData: GalleryItemOriginData(title: message.author?.displayTitle, timestamp: message.timestamp), indexData: location.flatMap { GalleryItemIndexData(position: Int32($0.index), totalCount: Int32($0.count)) }, contentInfo: .message(message), caption: NSAttributedString(string: ""), openUrl: openUrl, openUrlOptions: openUrlOptions) } } } @@ -254,6 +273,11 @@ class GalleryController: ViewController { private var hiddenMediaManagerIndex: Int? + private var openUrl: (String) -> Void + private var openUrlOptions: (String) -> Void + + private let resolveUrlDisposable = MetaDisposable() + init(account: Account, source: GalleryControllerItemSource, invertItemOrder: Bool = false, streamSingleVideo: Bool = false, synchronousLoad: Bool = false, replaceRootController: @escaping (ViewController, ValuePromise?) -> Void, baseNavigationController: NavigationController?) { self.account = account self.replaceRootController = replaceRootController @@ -262,6 +286,16 @@ class GalleryController: ViewController { self.presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } + var openLinkImpl: ((String) -> Void)? + self.openUrl = { url in + openLinkImpl?(url) + } + + var openLinkOptionsImpl: ((String) -> Void)? + self.openUrlOptions = { url in + openLinkOptionsImpl?(url) + } + super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: GalleryController.darkNavigationTheme, strings: NavigationBarStrings(presentationStrings: self.presentationData.strings))) let backItem = UIBarButtonItem(backButtonAppearanceWithTitle: presentationData.strings.Common_Back, target: self, action: #selector(self.donePressed)) @@ -351,7 +385,7 @@ class GalleryController: ViewController { var items: [GalleryItem] = [] var centralItemIndex: Int? for entry in strongSelf.entries { - if let item = galleryItemForEntry(account: account, presentationData: strongSelf.presentationData, entry: entry, streamVideos: streamSingleVideo) { + if let item = galleryItemForEntry(account: account, presentationData: strongSelf.presentationData, entry: entry, streamVideos: streamSingleVideo, openUrl: strongSelf.openUrl, openUrlOptions: strongSelf.openUrlOptions) { if case let .MessageEntry(message, _, _, _) = entry, message.stableId == strongSelf.centralEntryStableId { centralItemIndex = items.count } @@ -446,6 +480,68 @@ class GalleryController: ViewController { } }) } + + openLinkImpl = { [weak self] url in + if let strongSelf = self { + strongSelf.resolveUrlDisposable.set((resolveUrl(account: strongSelf.account, url: url) |> deliverOnMainQueue).start(next: { [weak self] result in + if let strongSelf = self { + let navigationController = strongSelf.baseNavigationController //strongSelf.navigationController as? NavigationController + openResolvedUrl(result, account: strongSelf.account, context: .chat, navigationController: navigationController, openPeer: { peerId, navigation in + self?.dismiss(forceAway: true) + switch navigation { + case let .chat(_, messageId): + if let navigationController = navigationController { + navigateToChatController(navigationController: navigationController, account: strongSelf.account, chatLocation: .peer(peerId), messageId: messageId) + } + case .info: + let _ = (strongSelf.account.postbox.loadedPeerWithId(peerId) + |> deliverOnMainQueue).start(next: { peer in + if let strongSelf = self, let navigationController = navigationController, let controller = peerInfoController(account: strongSelf.account, peer: peer) { + navigationController.pushViewController(controller) + } + }) + default: + break + } + }, present: { c, _ in + self?.present(c, in: .window(.root)) + }, dismissInput: { + self?.view.endEditing(true) + }) + } + })) + } + } + + openLinkOptionsImpl = { [weak self] url in + if let strongSelf = self { + let canOpenIn = availableOpenInOptions(applicationContext: account.telegramApplicationContext, item: .url(url: url)).count > 1 + let openText = canOpenIn ? strongSelf.presentationData.strings.Conversation_FileOpenIn : strongSelf.presentationData.strings.Conversation_LinkDialogOpen + let actionSheet = ActionSheetController(presentationTheme: strongSelf.presentationData.theme) + actionSheet.setItemGroups([ActionSheetItemGroup(items: [ + ActionSheetTextItem(title: url), + ActionSheetButtonItem(title: openText, color: .accent, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + openLinkImpl?(url) + }), + ActionSheetButtonItem(title: strongSelf.presentationData.strings.ShareMenu_CopyShareLink, color: .accent, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + UIPasteboard.general.string = url + }), + ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_AddToReadingList, color: .accent, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + if let link = URL(string: url) { + let _ = try? SSReadingList.default()?.addItem(with: link, title: nil, previewText: nil) + } + }) + ]), ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + }) + ])]) + strongSelf.present(actionSheet, in: .window(.root)) + } + } } required init(coder aDecoder: NSCoder) { @@ -454,6 +550,7 @@ class GalleryController: ViewController { deinit { self.disposable.dispose() + self.resolveUrlDisposable.dispose() self.centralItemAttributesDisposable.dispose() if let hiddenMediaManagerIndex = self.hiddenMediaManagerIndex, let mediaManager = self.account.telegramApplicationContext.mediaManager { mediaManager.galleryHiddenMediaManager.removeSource(hiddenMediaManagerIndex) @@ -561,7 +658,7 @@ class GalleryController: ViewController { var items: [GalleryItem] = [] var centralItemIndex: Int? for entry in self.entries { - if let item = galleryItemForEntry(account: account, presentationData: self.presentationData, entry: entry, streamVideos: self.streamVideos) { + if let item = galleryItemForEntry(account: account, presentationData: self.presentationData, entry: entry, streamVideos: self.streamVideos, openUrl: self.openUrl, openUrlOptions: self.openUrlOptions) { if case let .MessageEntry(message, _, _, _) = entry, message.stableId == self.centralEntryStableId { centralItemIndex = items.count } diff --git a/TelegramUI/GalleryControllerNode.swift b/TelegramUI/GalleryControllerNode.swift index 608dc1b3d0..b0ba9e75ce 100644 --- a/TelegramUI/GalleryControllerNode.swift +++ b/TelegramUI/GalleryControllerNode.swift @@ -127,7 +127,9 @@ class GalleryControllerNode: ASDisplayNode, UIScrollViewDelegate, UIGestureRecog if let index = items.index(where: { $0.isEqual(to: centralItem) }) { let convertedIndex = (index, progress) if strongSelf.currentThumbnailContainerNode?.groupId != centralId { - node = GalleryThumbnailContainerNode(groupId: centralId) + if items.count > 1 { + node = GalleryThumbnailContainerNode(groupId: centralId) + } } else { node = strongSelf.currentThumbnailContainerNode } diff --git a/TelegramUI/GameControllerNode.swift b/TelegramUI/GameControllerNode.swift index e70c4a08e1..925bb319a8 100644 --- a/TelegramUI/GameControllerNode.swift +++ b/TelegramUI/GameControllerNode.swift @@ -61,6 +61,9 @@ final class GameControllerNode: ViewControllerTracingNode { if #available(iOSApplicationExtension 9.0, *) { webView.allowsLinkPreview = false } + if #available(iOSApplicationExtension 11.0, *) { + webView.scrollView.contentInsetAdjustmentBehavior = .never + } webView.interactiveTransitionGestureRecognizerTest = { point -> Bool in return point.x > 30.0 } diff --git a/TelegramUI/GroupInfoController.swift b/TelegramUI/GroupInfoController.swift index 6a4795eb13..273f442d82 100644 --- a/TelegramUI/GroupInfoController.swift +++ b/TelegramUI/GroupInfoController.swift @@ -781,8 +781,11 @@ private func groupInfoEntries(account: Account, presentationData: PresentationDa if let editingState = state.editingState { if canEditGroupInfo { entries.append(GroupInfoEntry.setGroupPhoto(presentationData.theme, presentationData.strings.GroupInfo_SetGroupPhoto)) - entries.append(GroupInfoEntry.groupDescriptionSetup(presentationData.theme, presentationData.strings.Channel_Edit_AboutItem, editingState.editingDescriptionText)) - entries.append(GroupInfoEntry.groupDescriptionSetupInfo(presentationData.theme, isGroup ? presentationData.strings.Group_About_Help : presentationData.strings.Channel_About_Help)) + + if view.peers[view.peerId] is TelegramChannel { + entries.append(GroupInfoEntry.groupDescriptionSetup(presentationData.theme, presentationData.strings.Channel_Edit_AboutItem, editingState.editingDescriptionText)) + entries.append(GroupInfoEntry.groupDescriptionSetupInfo(presentationData.theme, isGroup ? presentationData.strings.Group_About_Help : presentationData.strings.Channel_About_Help)) + } } if let group = view.peers[view.peerId] as? TelegramGroup { diff --git a/TelegramUI/InstantImageGalleryItem.swift b/TelegramUI/InstantImageGalleryItem.swift index 938d5f1762..11759238ba 100644 --- a/TelegramUI/InstantImageGalleryItem.swift +++ b/TelegramUI/InstantImageGalleryItem.swift @@ -32,19 +32,23 @@ class InstantImageGalleryItem: GalleryItem { let account: Account let presentationData: PresentationData let imageReference: ImageMediaReference - let caption: String + let caption: NSAttributedString let location: InstantPageGalleryEntryLocation + let openUrl: (InstantPageUrlItem) -> Void + let openUrlOptions: (InstantPageUrlItem) -> Void - init(account: Account, presentationData: PresentationData, imageReference: ImageMediaReference, caption: String, location: InstantPageGalleryEntryLocation) { + init(account: Account, presentationData: PresentationData, imageReference: ImageMediaReference, caption: NSAttributedString, location: InstantPageGalleryEntryLocation, openUrl: @escaping (InstantPageUrlItem) -> Void, openUrlOptions: @escaping (InstantPageUrlItem) -> Void) { self.account = account self.presentationData = presentationData self.imageReference = imageReference self.caption = caption self.location = location + self.openUrl = openUrl + self.openUrlOptions = openUrlOptions } func node() -> GalleryItemNode { - let node = InstantImageGalleryItemNode(account: self.account, presentationData: self.presentationData) + let node = InstantImageGalleryItemNode(account: self.account, presentationData: self.presentationData, openUrl: self.openUrl, openUrlOptions: self.openUrlOptions) node.setImage(imageReference: self.imageReference) @@ -80,11 +84,13 @@ final class InstantImageGalleryItemNode: ZoomableContentGalleryItemNode { private var fetchDisposable = MetaDisposable() - init(account: Account, presentationData: PresentationData) { + init(account: Account, presentationData: PresentationData, openUrl: @escaping (InstantPageUrlItem) -> Void, openUrlOptions: @escaping (InstantPageUrlItem) -> Void) { self.account = account self.imageNode = TransformImageNode() self.footerContentNode = InstantPageGalleryFooterContentNode(account: account, presentationData: presentationData) + self.footerContentNode.openUrl = openUrl + self.footerContentNode.openUrlOptions = openUrlOptions super.init() @@ -108,7 +114,7 @@ final class InstantImageGalleryItemNode: ZoomableContentGalleryItemNode { super.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition) } - fileprivate func setCaption(_ caption: String) { + fileprivate func setCaption(_ caption: NSAttributedString) { self.footerContentNode.setCaption(caption) } diff --git a/TelegramUI/InstantPageAnchorItem.swift b/TelegramUI/InstantPageAnchorItem.swift index 4246c3c7e1..660e628d8a 100644 --- a/TelegramUI/InstantPageAnchorItem.swift +++ b/TelegramUI/InstantPageAnchorItem.swift @@ -22,7 +22,7 @@ final class InstantPageAnchorItem: InstantPageItem { func drawInTile(context: CGContext) { } - func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void) -> (InstantPageNode & ASDisplayNode)? { + func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (Int, Int) -> Void) -> (InstantPageNode & ASDisplayNode)? { return nil } diff --git a/TelegramUI/InstantPageArticleItem.swift b/TelegramUI/InstantPageArticleItem.swift new file mode 100644 index 0000000000..c6a8c15ebd --- /dev/null +++ b/TelegramUI/InstantPageArticleItem.swift @@ -0,0 +1,62 @@ +import Foundation +import Postbox +import TelegramCore +import AsyncDisplayKit + +final class InstantPageArticleItem: InstantPageItem { + var frame: CGRect + let wantsNode: Bool = true + let medias: [InstantPageMedia] = [] + let webPage: TelegramMediaWebpage + + let title: String + let description: String + let cover: TelegramMediaImage? + let url: String + let webpageId: MediaId + + init(frame: CGRect, webPage: TelegramMediaWebpage, title: String, description: String, cover: TelegramMediaImage?, url: String, webpageId: MediaId) { + self.frame = frame + self.webPage = webPage + self.title = title + self.description = description + self.cover = cover + self.url = url + self.webpageId = webpageId + } + + func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (Int, Int) -> Void) -> (InstantPageNode & ASDisplayNode)? { + return InstantPageArticleNode(account: account, webPage: self.webPage, strings: strings, theme: theme, title: self.title, description: self.description, cover: self.cover, url: self.url, webpageId: self.webpageId, openUrl: openUrl) + } + + func matchesAnchor(_ anchor: String) -> Bool { + return false + } + + func matchesNode(_ node: InstantPageNode) -> Bool { + if let node = node as? InstantPageArticleNode { + return self.webpageId == node.webpageId + } else { + return false + } + } + + func distanceThresholdGroup() -> Int? { + return 7 + } + + func distanceThresholdWithGroupCount(_ count: Int) -> CGFloat { + if count > 3 { + return 1000.0 + } else { + return CGFloat.greatestFiniteMagnitude + } + } + + func drawInTile(context: CGContext) { + } + + func linkSelectionRects(at point: CGPoint) -> [CGRect] { + return [] + } +} diff --git a/TelegramUI/InstantPageArticleNode.swift b/TelegramUI/InstantPageArticleNode.swift new file mode 100644 index 0000000000..8b9ed2428f --- /dev/null +++ b/TelegramUI/InstantPageArticleNode.swift @@ -0,0 +1,159 @@ +import Foundation +import AsyncDisplayKit +import Display +import Postbox +import TelegramCore +import SwiftSignalKit + +private func isRtl(string: String) -> Bool { + if string.count == 0 { + return false + } + + let code = CFStringTokenizerCopyBestStringLanguage(string as CFString, CFRangeMake(0, string.count)) as String + return Locale.characterDirection(forLanguage: code) == .rightToLeft +} + +final class InstantPageArticleNode: ASDisplayNode, InstantPageNode { + private let titleNode: ASTextNode + private let descriptionNode: ASTextNode + private var imageNode: TransformImageNode? + + private let highlightedBackgroundNode: ASDisplayNode + private let buttonNode: HighlightableButtonNode + + let title: String + let pageDescription: String + let url: String + let webpageId: MediaId + let cover: TelegramMediaImage? + let rtl: Bool + + private let openUrl: (InstantPageUrlItem) -> Void + + private var fetchedDisposable = MetaDisposable() + + init(account: Account, webPage: TelegramMediaWebpage, strings: PresentationStrings, theme: InstantPageTheme, title: String, description: String, cover: TelegramMediaImage?, url: String, webpageId: MediaId, openUrl: @escaping (InstantPageUrlItem) -> Void) { + self.title = title + self.pageDescription = description + self.url = url + self.webpageId = webpageId + self.cover = cover + self.rtl = isRtl(string: title) || isRtl(string: description) + self.openUrl = openUrl + + self.highlightedBackgroundNode = ASDisplayNode() + self.highlightedBackgroundNode.isLayerBacked = true + self.highlightedBackgroundNode.alpha = 0.0 + + self.buttonNode = HighlightableButtonNode() + + self.titleNode = ASTextNode() + self.titleNode.isLayerBacked = true + self.titleNode.maximumNumberOfLines = 1 + + self.descriptionNode = ASTextNode() + self.descriptionNode.isLayerBacked = true + self.descriptionNode.maximumNumberOfLines = 2 + + super.init() + + self.addSubnode(self.highlightedBackgroundNode) + self.addSubnode(self.buttonNode) + self.addSubnode(self.titleNode) + self.addSubnode(self.descriptionNode) + + if let image = cover { + let imageNode = TransformImageNode() + imageNode.isUserInteractionEnabled = false + + let imageReference = ImageMediaReference.webPage(webPage: WebpageReference(webPage), media: image) + imageNode.setSignal(chatMessagePhoto(postbox: account.postbox, photoReference: imageReference)) + self.fetchedDisposable.set(chatMessagePhotoInteractiveFetched(account: account, photoReference: imageReference, storeToDownloadsPeerType: nil).start()) + + self.imageNode = imageNode + self.addSubnode(imageNode) + } + + self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) + + self.buttonNode.highligthedChanged = { [weak self] highlighted in + if let strongSelf = self { + if highlighted { + strongSelf.highlightedBackgroundNode.layer.removeAnimation(forKey: "opacity") + strongSelf.highlightedBackgroundNode.alpha = 1.0 + } else { + strongSelf.highlightedBackgroundNode.alpha = 0.0 + strongSelf.highlightedBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2) + } + } + } + + self.update(strings: strings, theme: theme) + } + + deinit { + self.fetchedDisposable.dispose() + } + + @objc func buttonPressed() { + self.openUrl(InstantPageUrlItem(url: self.url, webpageId: self.webpageId)) + } + + override func layout() { + super.layout() + + let size = self.bounds.size + let inset: CGFloat = 17.0 + let imageSize = CGSize(width: 65.0, height: 65.0) + + self.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: size.width, height: size.height + UIScreenPixel)) + self.buttonNode.frame = CGRect(origin: CGPoint(), size: size) + + var sideInset: CGFloat = 0.0 + if let imageNode = self.imageNode, let image = self.cover, let largest = largestImageRepresentation(image.representations) { + sideInset = imageSize.width + inset + let size = largest.dimensions.aspectFilled(imageSize) + let boundingSize = imageSize + + let makeLayout = imageNode.asyncLayout() + let apply = makeLayout(TransformImageArguments(corners: ImageCorners(radius: 5.0), imageSize: size, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets())) + apply() + } + + let titleSize = self.titleNode.measure(CGSize(width: size.width - inset * 2.0 - sideInset, height: size.height)) + let descriptionSize = self.descriptionNode.measure(CGSize(width: size.width - inset * 2.0 - sideInset, height: size.height)) + + if self.rtl { + if let imageNode = self.imageNode { + imageNode.frame = CGRect(origin: CGPoint(x: inset, y: floor((size.height - imageSize.height) / 2.0)), size: imageSize) + } + self.titleNode.frame = CGRect(origin: CGPoint(x: size.width - titleSize.width - inset, y: 16.0), size: titleSize) + self.descriptionNode.frame = CGRect(origin: CGPoint(x: size.width - descriptionSize.width - inset, y: self.titleNode.frame.maxY + 6.0), size: descriptionSize) + } else { + if let imageNode = self.imageNode { + imageNode.frame = CGRect(origin: CGPoint(x: size.width - inset - imageSize.width, y: floor((size.height - imageSize.height) / 2.0)), size: imageSize) + } + self.titleNode.frame = CGRect(origin: CGPoint(x: inset, y: 16.0), size: titleSize) + self.descriptionNode.frame = CGRect(origin: CGPoint(x: inset, y: self.titleNode.frame.maxY + 6.0), size: descriptionSize) + } + } + + func updateIsVisible(_ isVisible: Bool) { + + } + + func transitionNode(media: InstantPageMedia) -> (ASDisplayNode, () -> UIView?)? { + return nil + } + + func updateHiddenMedia(media: InstantPageMedia?) { + + } + + func update(strings: PresentationStrings, theme: InstantPageTheme) { + self.titleNode.attributedText = NSAttributedString(string: self.title, font: UIFont(name: "Georgia", size: 17.0), textColor: theme.panelPrimaryColor) + self.descriptionNode.attributedText = NSAttributedString(string: self.pageDescription, font: theme.serif ? UIFont(name: "Georgia", size: 15.0) : Font.regular(15.0), textColor: theme.panelSecondaryColor) + self.highlightedBackgroundNode.backgroundColor = theme.panelHighlightedBackgroundColor + } +} diff --git a/TelegramUI/InstantPageAudioItem.swift b/TelegramUI/InstantPageAudioItem.swift index db651fd9da..20867a4af2 100644 --- a/TelegramUI/InstantPageAudioItem.swift +++ b/TelegramUI/InstantPageAudioItem.swift @@ -18,7 +18,7 @@ final class InstantPageAudioItem: InstantPageItem { self.medias = [media] } - func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void) -> (InstantPageNode & ASDisplayNode)? { + func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (Int, Int) -> Void) -> (InstantPageNode & ASDisplayNode)? { return InstantPageAudioNode(account: account, strings: strings, theme: theme, webPage: self.webpage, media: self.media, openMedia: openMedia) } diff --git a/TelegramUI/InstantPageController.swift b/TelegramUI/InstantPageController.swift index c4db418347..bb099b3dc2 100644 --- a/TelegramUI/InstantPageController.swift +++ b/TelegramUI/InstantPageController.swift @@ -76,9 +76,9 @@ final class InstantPageController: ViewController { return self?.navigationController as? NavigationController }, present: { [weak self] c, a in self?.present(c, in: .window(.root), with: a) - }, pushController: { [weak self] c in - (self?.navigationController as? NavigationController)?.pushViewController(c) - }, openPeer: { [weak self] peerId in + }, pushController: { [weak self] c in + (self?.navigationController as? NavigationController)?.pushViewController(c) + }, openPeer: { [weak self] peerId in if let strongSelf = self { (strongSelf.navigationController as? NavigationController)?.pushViewController(ChatController(account: strongSelf.account, chatLocation: .peer(peerId))) } diff --git a/TelegramUI/InstantPageControllerNode.swift b/TelegramUI/InstantPageControllerNode.swift index a7f2ebfa73..bb189a59ff 100644 --- a/TelegramUI/InstantPageControllerNode.swift +++ b/TelegramUI/InstantPageControllerNode.swift @@ -29,6 +29,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { private let navigationBar: InstantPageNavigationBar private let scrollNode: ASScrollNode private let scrollNodeHeader: ASDisplayNode + private let scrollNodeFooter: ASDisplayNode private var linkHighlightingNode: LinkHighlightingNode? private var textSelectionNode: LinkHighlightingNode? private var settingsNode: InstantPageSettingsNode? @@ -36,11 +37,13 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { var currentLayout: InstantPageLayout? var currentLayoutTiles: [InstantPageTile] = [] - var currentLayoutItemsWithViews: [InstantPageItem] = [] + var currentLayoutItemsWithNodes: [InstantPageItem] = [] var distanceThresholdGroupCount: [Int: Int] = [:] var visibleTiles: [Int: InstantPageTileNode] = [:] - var visibleItemsWithViews: [Int: InstantPageNode] = [:] + var visibleItemsWithNodes: [Int: InstantPageNode] = [:] + + var currentWebEmbedHeights: [Int: Int] = [:] var previousContentOffset: CGPoint? var isDeceleratingBecauseOfDragging = false @@ -49,6 +52,8 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { private let resolveUrlDisposable = MetaDisposable() private let loadWebpageDisposable = MetaDisposable() + private let updateLayoutDisposable = MetaDisposable() + private var themeReferenceDate: Date? init(account: Account, settings: InstantPagePresentationSettings?, presentationTheme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, statusBar: StatusBar, getNavigationController: @escaping () -> NavigationController?, present: @escaping (ViewController, Any?) -> Void, pushController: @escaping (ViewController) -> Void, openPeer: @escaping (PeerId) -> Void, navigateBack: @escaping () -> Void) { @@ -74,6 +79,9 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { self.scrollNodeHeader = ASDisplayNode() self.scrollNodeHeader.backgroundColor = .black + self.scrollNodeFooter = ASDisplayNode() + self.scrollNodeFooter.backgroundColor = .black + super.init() self.setViewBlock({ @@ -82,9 +90,11 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { if let theme = self.theme { self.backgroundColor = theme.pageBackgroundColor + self.scrollNodeFooter.backgroundColor = theme.panelBackgroundColor } self.addSubnode(self.scrollNode) self.scrollNode.addSubnode(self.scrollNodeHeader) + self.scrollNode.addSubnode(self.scrollNodeFooter) self.addSubnode(self.navigationBar) self.scrollNode.view.delaysContentTouches = false self.scrollNode.view.delegate = self @@ -159,7 +169,8 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { self.updateLayout() - for (_, itemNode) in self.visibleItemsWithViews { + self.scrollNodeFooter.backgroundColor = theme.panelBackgroundColor + for (_, itemNode) in self.visibleItemsWithNodes { itemNode.update(strings: strings, theme: theme) } @@ -189,6 +200,10 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { return .fail } else if item is InstantPageAudioItem { return .fail + } else if item is InstantPageArticleItem { + return .fail + } else if item is InstantPageFeedbackItem { + return .fail } break } @@ -242,8 +257,6 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { transition.updateFrame(node: settingsNode, frame: CGRect(origin: CGPoint(), size: layout.size)) } - let statusBarHeight: CGFloat = layout.statusBarHeight ?? 0.0 - let maxBarHeight: CGFloat if !layout.safeInsets.top.isZero { maxBarHeight = layout.safeInsets.top + 34.0 @@ -260,7 +273,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { if self.scrollNode.bounds.size != layout.size || !self.scrollNode.view.contentInset.top.isEqual(to: scrollInsetTop) { self.scrollNode.frame = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height) self.scrollNodeHeader.frame = CGRect(origin: CGPoint(x: 0.0, y: -2000.0), size: CGSize(width: layout.size.width, height: 2000.0)) - self.scrollNode.view.contentInset = UIEdgeInsetsMake(scrollInsetTop, 0.0, 0.0, 0.0) + self.scrollNode.view.contentInset = UIEdgeInsetsMake(scrollInsetTop, 0.0, layout.intrinsicInsets.bottom, 0.0) if widthUpdated { self.updateLayout() } @@ -294,7 +307,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { return } - let currentLayout = instantPageLayoutForWebPage(webPage, boundingWidth: containerLayout.size.width, safeInset: containerLayout.safeInsets.left, strings: self.strings, theme: theme, dateTimeFormat: self.dateTimeFormat) + let currentLayout = instantPageLayoutForWebPage(webPage, boundingWidth: containerLayout.size.width, safeInset: containerLayout.safeInsets.left, strings: self.strings, theme: theme, dateTimeFormat: self.dateTimeFormat, webEmbedHeights: self.currentWebEmbedHeights) for (_, tileNode) in self.visibleTiles { tileNode.removeFromSupernode() @@ -304,7 +317,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { let currentLayoutTiles = instantPageTilesFromLayout(currentLayout, boundingWidth: containerLayout.size.width) var currentLayoutItemsWithViews: [InstantPageItem] = [] - var distanceThresholdGroupCount: [Int: Int] = [:] + var distanceThresholdGroupCount: [Int : Int] = [:] for item in currentLayout.items { if item.wantsNode { @@ -323,10 +336,12 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { self.currentLayout = currentLayout self.currentLayoutTiles = currentLayoutTiles - self.currentLayoutItemsWithViews = currentLayoutItemsWithViews + self.currentLayoutItemsWithNodes = currentLayoutItemsWithViews self.distanceThresholdGroupCount = distanceThresholdGroupCount self.scrollNode.view.contentSize = currentLayout.contentSize + + self.scrollNodeFooter.frame = CGRect(origin: CGPoint(x: 0.0, y: currentLayout.contentSize.height), size: CGSize(width: containerLayout.size.width, height: 2000.0)) } func updateVisibleItems() { @@ -373,7 +388,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { } var itemIndex = -1 - for item in self.currentLayoutItemsWithViews { + for item in self.currentLayoutItemsWithNodes { itemIndex += 1 var itemThreshold: CGFloat = 0.0 if let group = item.distanceThresholdGroup() { @@ -389,11 +404,11 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { if visibleBounds.intersects(itemFrame) { visibleItemIndices.insert(itemIndex) - var itemNode = self.visibleItemsWithViews[itemIndex] + var itemNode = self.visibleItemsWithNodes[itemIndex] if let currentItemNode = itemNode { if !item.matchesNode(currentItemNode) { (currentItemNode as! ASDisplayNode).removeFromSupernode() - self.visibleItemsWithViews.removeValue(forKey: itemIndex) + self.visibleItemsWithNodes.removeValue(forKey: itemIndex) itemNode = nil } } @@ -403,6 +418,10 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { self?.openMedia(media) }, openPeer: { [weak self] peerId in self?.openPeer(peerId) + }, openUrl: { [weak self] url in + self?.openUrl(url) + }, updateWebEmbedHeight: { [weak self] key, height in + self?.updateWebEmbedHeight(key, height) }) { itemNode.frame = item.frame if let topNode = topNode { @@ -411,7 +430,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { self.scrollNode.insertSubnode(itemNode, at: 0) } topNode = itemNode - self.visibleItemsWithViews[itemIndex] = itemNode + self.visibleItemsWithNodes[itemIndex] = itemNode } } else { if (itemNode as! ASDisplayNode).frame != item.frame { @@ -433,7 +452,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { } var removeItemIndices: [Int] = [] - for (index, itemNode) in self.visibleItemsWithViews { + for (index, itemNode) in self.visibleItemsWithNodes { if !visibleItemIndices.contains(index) { removeItemIndices.append(index) (itemNode as! ASDisplayNode).removeFromSupernode() @@ -446,7 +465,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { } } for index in removeItemIndices { - self.visibleItemsWithViews.removeValue(forKey: index) + self.visibleItemsWithNodes.removeValue(forKey: index) } } @@ -540,7 +559,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { self.navigationBar.updateLayout(size: navigationBarFrame.size, minHeight: minBarHeight, maxHeight: maxBarHeight, topInset: containerLayout.safeInsets.top, leftInset: containerLayout.safeInsets.left, rightInset: containerLayout.safeInsets.right, pageProgress: pageProgress, transition: transition) transition.animateView { - self.scrollNode.view.scrollIndicatorInsets = UIEdgeInsets(top: navigationBarFrame.size.height, left: 0.0, bottom: 0.0, right: 0.0) + self.scrollNode.view.scrollIndicatorInsets = UIEdgeInsets(top: navigationBarFrame.size.height, left: 0.0, bottom: containerLayout.intrinsicInsets.bottom, right: 0.0) } } @@ -549,10 +568,15 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { if let location = location, let currentLayout = self.currentLayout { for item in currentLayout.items { if item.frame.contains(location) { - let textNodeFrame = item.frame + let itemNodeFrame = item.frame var itemRects = item.linkSelectionRects(at: location.offsetBy(dx: -item.frame.minX, dy: -item.frame.minY)) + var contentOffset = CGPoint() + if let item = item as? InstantPageTableItem { + contentOffset = tableContentOffset(item: item) + } + for i in 0 ..< itemRects.count { - itemRects[i] = itemRects[i].offsetBy(dx: textNodeFrame.minX, dy: textNodeFrame.minY).insetBy(dx: -2.0, dy: -2.0) + itemRects[i] = itemRects[i].offsetBy(dx: itemNodeFrame.minX - contentOffset.x, dy: itemNodeFrame.minY).insetBy(dx: -2.0, dy: -2.0) } if !itemRects.isEmpty { rects = itemRects @@ -567,7 +591,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { if let current = self.linkHighlightingNode { linkHighlightingNode = current } else { - linkHighlightingNode = LinkHighlightingNode(color: UIColor(rgb: 0x007BE8).withAlphaComponent(0.4)) + linkHighlightingNode = LinkHighlightingNode(color: UIColor(rgb: 0x007be8).withAlphaComponent(0.4)) linkHighlightingNode.isUserInteractionEnabled = false self.linkHighlightingNode = linkHighlightingNode self.scrollNode.addSubnode(linkHighlightingNode) @@ -582,11 +606,27 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { } } - private func textItemAtLocation(_ location: CGPoint) -> InstantPageTextItem? { + private func tableContentOffset(item: InstantPageTableItem) -> CGPoint { + var contentOffset = CGPoint() + for (_, itemNode) in self.visibleItemsWithNodes { + if let itemNode = itemNode as? InstantPageTableNode, itemNode.item === item { + contentOffset = itemNode.contentOffset + break + } + } + return contentOffset + } + + private func textItemAtLocation(_ location: CGPoint) -> (InstantPageTextItem, CGPoint)? { if let currentLayout = self.currentLayout { for item in currentLayout.items { - if let item = item as? InstantPageTextItem, item.frame.contains(location) { - return item + if let item = item as? InstantPageTextItem, item.selectable, item.frame.contains(location) { + return (item, CGPoint()) + } else if let item = item as? InstantPageTableItem, item.frame.contains(location) { + let contentOffset = tableContentOffset(item: item) + if let (textItem, parentOffset) = item.textItemAtLocation(location.offsetBy(dx: -item.frame.minX + contentOffset.x, dy: -item.frame.minY)) { + return (textItem, item.frame.origin.offsetBy(dx: parentOffset.x - contentOffset.x, dy: parentOffset.y)) + } } } } @@ -594,8 +634,8 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { } private func urlForTapLocation(_ location: CGPoint) -> InstantPageUrlItem? { - if let item = self.textItemAtLocation(location) { - return item.urlAttribute(at: location.offsetBy(dx: -item.frame.minX, dy: -item.frame.minY)) + if let (item, parentOffset) = self.textItemAtLocation(location) { + return item.urlAttribute(at: location.offsetBy(dx: -item.frame.minX - parentOffset.x, dy: -item.frame.minY - parentOffset.y)) } return nil } @@ -638,11 +678,11 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { }) ])]) self.present(actionSheet, nil) - } else if let item = self.textItemAtLocation(location) { + } else if let (item, parentOffset) = self.textItemAtLocation(location) { let textNodeFrame = item.frame var itemRects = item.lineRects() for i in 0 ..< itemRects.count { - itemRects[i] = itemRects[i].offsetBy(dx: textNodeFrame.minX, dy: textNodeFrame.minY).insetBy(dx: -2.0, dy: -2.0) + itemRects[i] = itemRects[i].offsetBy(dx: parentOffset.x + textNodeFrame.minX, dy: parentOffset.y + textNodeFrame.minY).insetBy(dx: -2.0, dy: -2.0) } self.updateTextSelectionRects(itemRects, text: item.plainText()) } @@ -770,6 +810,12 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { return } + if let map = media.media as? TelegramMediaMap { + let controller = legacyLocationController(message: nil, mapMedia: map, account: self.account, modal: false, openPeer: { _ in }, sendLiveLocation: { _, _ in }, stopLiveLocation: { }, openUrl: { _ in }) + self.pushController(controller) + return + } + if let file = media.media as? TelegramMediaFile, (file.isVoice || file.isMusic) { var medias: [InstantPageMedia] = [] var initialIndex = 0 @@ -798,7 +844,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { var entries: [InstantPageGalleryEntry] = [] for media in medias { - entries.append(InstantPageGalleryEntry(index: Int32(media.index), pageId: webPage.webpageId, media: media, caption: media.caption ?? "", location: InstantPageGalleryEntryLocation(position: Int32(entries.count), totalCount: Int32(medias.count)))) + entries.append(InstantPageGalleryEntry(index: Int32(media.index), pageId: webPage.webpageId, media: media, caption: media.caption, location: InstantPageGalleryEntryLocation(position: Int32(entries.count), totalCount: Int32(medias.count)))) } var centralIndex: Int? @@ -811,17 +857,17 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { if let centralIndex = centralIndex { let controller = InstantPageGalleryController(account: self.account, webPage: webPage, entries: entries, centralIndex: centralIndex, replaceRootController: { _, _ in - }) + }, baseNavigationController: self.getNavigationController()) self.hiddenMediaDisposable.set((controller.hiddenMedia |> deliverOnMainQueue).start(next: { [weak self] entry in if let strongSelf = self { - for (_, itemNode) in strongSelf.visibleItemsWithViews { + for (_, itemNode) in strongSelf.visibleItemsWithNodes { itemNode.updateHiddenMedia(media: entry?.media) } } })) self.present(controller, InstantPageGalleryControllerPresentationArguments(transitionArguments: { [weak self] entry -> GalleryTransitionArguments? in if let strongSelf = self { - for (_, itemNode) in strongSelf.visibleItemsWithViews { + for (_, itemNode) in strongSelf.visibleItemsWithNodes { if let transitionNode = itemNode.transitionNode(media: entry.media) { return GalleryTransitionArguments(transitionNode: transitionNode, addToTransitionSurface: { _ in }) @@ -833,6 +879,24 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { } } + private func updateWebEmbedHeight(_ key: Int, _ height: Int) { + let currentHeight = self.currentWebEmbedHeights[key] + if height != currentHeight { + if let currentHeight = currentHeight, currentHeight > height { + return + } + self.currentWebEmbedHeights[key] = height + + let signal: Signal = (.complete() |> delay(0.08, queue: Queue.mainQueue())) + self.updateLayoutDisposable.set(signal.start(completed: { [weak self] in + if let strongSelf = self { + strongSelf.updateLayout() + strongSelf.updateVisibleItems() + } + })) + } + } + private func presentSettings() { guard let settings = self.settings, let containerLayout = self.containerLayout else { return diff --git a/TelegramUI/InstantPageDetailsItem.swift b/TelegramUI/InstantPageDetailsItem.swift new file mode 100644 index 0000000000..4771b17a7b --- /dev/null +++ b/TelegramUI/InstantPageDetailsItem.swift @@ -0,0 +1,63 @@ +import Foundation +import Postbox +import TelegramCore +import AsyncDisplayKit + +final class InstantPageDetailsItem: InstantPageItem { + var frame: CGRect + let wantsNode: Bool = true + let medias: [InstantPageMedia] = [] + + let title: NSAttributedString + let items: [InstantPageItem] + let rtl: Bool + + init(frame: CGRect, title: NSAttributedString, items: [InstantPageItem], rtl: Bool) { + self.frame = frame + self.title = title + self.items = items + self.rtl = rtl + } + + func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (Int, Int) -> Void) -> (InstantPageNode & ASDisplayNode)? { + return InstantPageDetailsNode(account: account, strings: strings, theme: theme, item: self) + } + + func matchesAnchor(_ anchor: String) -> Bool { + return false + } + + func matchesNode(_ node: InstantPageNode) -> Bool { + if let node = node as? InstantPageDetailsNode { + return self === node.item + } else { + return false + } + } + + func distanceThresholdGroup() -> Int? { + return 8 + } + + func distanceThresholdWithGroupCount(_ count: Int) -> CGFloat { + if count > 3 { + return 1000.0 + } else { + return CGFloat.greatestFiniteMagnitude + } + } + + func drawInTile(context: CGContext) { + } + + func linkSelectionRects(at point: CGPoint) -> [CGRect] { + return [] + } +} + +func layoutDetailsItem(theme: InstantPageTheme, title: NSAttributedString, boundingWidth: CGFloat, items: [InstantPageItem], contentSize: CGSize, open: Bool, rtl: Bool) -> InstantPageDetailsItem { + for var item in items { + item.frame = item.frame.offsetBy(dx: 0.0, dy: 44.0) + } + return InstantPageDetailsItem(frame: CGRect(x: 0.0, y: 0.0, width: boundingWidth, height: 44.0), title: title, items: items, rtl: rtl) +} diff --git a/TelegramUI/InstantPageDetailsNode.swift b/TelegramUI/InstantPageDetailsNode.swift new file mode 100644 index 0000000000..4d13bb505e --- /dev/null +++ b/TelegramUI/InstantPageDetailsNode.swift @@ -0,0 +1,241 @@ +import Foundation +import AsyncDisplayKit +import Display +import Postbox +import TelegramCore +import SwiftSignalKit + +private let detailsHeaderHeight: CGFloat = 44.0 +private let detailsInset: CGFloat = 17.0 +private let titleInset: CGFloat = 22.0 + +final class InstantPageDetailsNode: ASDisplayNode, InstantPageNode { + let item: InstantPageDetailsItem + + private let titleTile: InstantPageTile + private let titleTileNode: InstantPageTileNode + + private let highlightedBackgroundNode: ASDisplayNode + private let buttonNode: HighlightableButtonNode + private let arrowNode: InstantPageDetailsArrowNode + private let separatorNode: ASDisplayNode + + init(account: Account, strings: PresentationStrings, theme: InstantPageTheme, item: InstantPageDetailsItem) { + self.item = item + let frame = item.frame + + self.highlightedBackgroundNode = ASDisplayNode() + self.highlightedBackgroundNode.isLayerBacked = true + self.highlightedBackgroundNode.alpha = 0.0 + + self.buttonNode = HighlightableButtonNode() + + self.titleTile = InstantPageTile(frame: CGRect(x: 0.0, y: 0.0, width: frame.width, height: detailsHeaderHeight)) + self.titleTileNode = InstantPageTileNode(tile: self.titleTile, backgroundColor: .clear) + + let titleItems = layoutTextItemWithString(item.title, boundingWidth: frame.size.width - detailsInset * 2.0 - titleInset, offset: CGPoint(x: detailsInset + titleInset, y: 0.0)).0 + var offset: CGFloat? + for var item in titleItems { + var itemOffset = floorToScreenPixels((detailsHeaderHeight - item.frame.height) / 2.0) + if item is InstantPageTextItem { + offset = itemOffset + } else if let offset = offset { + itemOffset = offset + } + item.frame = item.frame.offsetBy(dx: 0.0, dy: itemOffset) + } + self.titleTile.items.append(contentsOf: titleItems) + + self.arrowNode = InstantPageDetailsArrowNode(color: theme.controlColor, open: false) + self.separatorNode = ASDisplayNode() + + super.init() + + self.addSubnode(self.highlightedBackgroundNode) + self.addSubnode(self.buttonNode) + self.addSubnode(self.titleTileNode) + self.addSubnode(self.arrowNode) + self.addSubnode(self.separatorNode) + + let lineSize = CGSize(width: frame.width - detailsInset, height: UIScreenPixel) + self.separatorNode.frame = CGRect(origin: CGPoint(x: item.rtl ? 0.0 : detailsInset, y: detailsHeaderHeight - lineSize.height), size: lineSize) + + self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) + + self.buttonNode.highligthedChanged = { [weak self] highlighted in + if let strongSelf = self { + if highlighted { + strongSelf.highlightedBackgroundNode.layer.removeAnimation(forKey: "opacity") + strongSelf.highlightedBackgroundNode.alpha = 1.0 + if strongSelf.separatorNode.frame.minY < strongSelf.highlightedBackgroundNode.frame.maxY { + strongSelf.separatorNode.alpha = 0.0 + } + } else { + strongSelf.highlightedBackgroundNode.alpha = 0.0 + strongSelf.highlightedBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2) + if strongSelf.separatorNode.alpha < 1.0 { + strongSelf.separatorNode.alpha = 1.0 + strongSelf.separatorNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + } + } + } + + self.update(strings: strings, theme: theme) + } + + @objc func buttonPressed() { + self.arrowNode.setOpen(!self.arrowNode.open, animated: true) + //self.openUrl(InstantPageUrlItem(url: self.url, webpageId: self.webpageId)) + } + + override func layout() { + super.layout() + + let size = self.bounds.size + + self.titleTileNode.frame = self.titleTile.frame + self.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: detailsHeaderHeight + UIScreenPixel)) + self.buttonNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: detailsHeaderHeight)) + self.arrowNode.frame = CGRect(x: detailsInset, y: floorToScreenPixels((detailsHeaderHeight - 8.0) / 2.0) + 1.0, width: 13.0, height: 8.0) + } + + func updateIsVisible(_ isVisible: Bool) { + + } + + func transitionNode(media: InstantPageMedia) -> (ASDisplayNode, () -> UIView?)? { + return nil + } + + func updateHiddenMedia(media: InstantPageMedia?) { + + } + + func update(strings: PresentationStrings, theme: InstantPageTheme) { +// self.titleNode.attributedText = NSAttributedString(string: self.title, font: UIFont(name: "Georgia", size: 17.0), textColor: theme.panelPrimaryColor) +// self.descriptionNode.attributedText = NSAttributedString(string: self.pageDescription, font: theme.serif ? UIFont(name: "Georgia", size: 15.0) : Font.regular(15.0), textColor: theme.panelSecondaryColor) + self.arrowNode.color = theme.controlColor + self.separatorNode.backgroundColor = theme.controlColor + self.highlightedBackgroundNode.backgroundColor = theme.panelHighlightedBackgroundColor + } +} + +private final class InstantPageDetailsArrowNodeParameters: NSObject { + let color: UIColor + let progress: CGFloat + + init(color: UIColor, progress: CGFloat) { + self.color = color + self.progress = progress + } +} + +final class InstantPageDetailsArrowNode : ASDisplayNode { + var color: UIColor { + didSet { + self.setNeedsDisplay() + } + } + private (set) var open: Bool + + private var progress: CGFloat = 0.0 + private var targetProgress: CGFloat? + + private var displayLink: CADisplayLink? + + init(color: UIColor, open: Bool) { + self.color = color + self.open = open + self.progress = open ? 1.0 : 0.0 + + super.init() + + self.isOpaque = false + self.isLayerBacked = true + + class DisplayLinkProxy: NSObject { + weak var target: InstantPageDetailsArrowNode? + init(target: InstantPageDetailsArrowNode) { + self.target = target + } + + @objc func displayLinkEvent() { + self.target?.displayLinkEvent() + } + } + + self.displayLink = CADisplayLink(target: DisplayLinkProxy(target: self), selector: #selector(DisplayLinkProxy.displayLinkEvent)) + self.displayLink?.isPaused = true + self.displayLink?.add(to: RunLoop.main, forMode: RunLoopMode.commonModes) + } + + deinit { + self.displayLink?.invalidate() + } + + func setOpen(_ open: Bool, animated: Bool) { + let openProgress: CGFloat = open ? 1.0 : 0.0 + if animated { + self.targetProgress = openProgress + self.displayLink?.isPaused = false + } else { + self.progress = openProgress + self.targetProgress = nil + self.displayLink?.isPaused = true + } + } + + override func willEnterHierarchy() { + super.willEnterHierarchy() + if self.targetProgress != nil { + self.displayLink?.isPaused = false + } + } + + override func didExitHierarchy() { + super.didExitHierarchy() + self.displayLink?.isPaused = true + } + + private func displayLinkEvent() { + if let targetProgress = self.targetProgress { +// var fps: Int = 60 +// if let link = self.displayLink, link.duration > 0 { +// fps = Int(round(1000 / link.duration) / 1000) +// } + let delta = targetProgress - self.progress + self.progress += delta * 0.01 + if delta > 0 && self.progress > targetProgress { + self.progress = 1.0 + self.targetProgress = nil + self.displayLink?.isPaused = true + } else if delta < 0 && self.progress < targetProgress { + self.progress = 0.0 + self.targetProgress = nil + self.displayLink?.isPaused = true + } + } + + self.setNeedsDisplay() + } + + override func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? { + return InstantPageDetailsArrowNodeParameters(color: self.color, progress: self.progress) + } + + @objc override class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) { + let context = UIGraphicsGetCurrentContext()! + + if let parameters = parameters as? InstantPageDetailsArrowNodeParameters { + context.setStrokeColor(parameters.color.cgColor) + context.setLineCap(.round) + context.setLineWidth(2.0) + + context.move(to: CGPoint(x: 1.0, y: 1.0 + 5.0 * parameters.progress)) + context.addLine(to: CGPoint(x: 6.0, y: 6.0 - 5.0 * parameters.progress)) + context.addLine(to: CGPoint(x: 11.0, y: 1.0 + 5.0 * parameters.progress)) + context.strokePath() + } + } +} diff --git a/TelegramUI/InstantPageFeedbackItem.swift b/TelegramUI/InstantPageFeedbackItem.swift new file mode 100644 index 0000000000..01d0eab99e --- /dev/null +++ b/TelegramUI/InstantPageFeedbackItem.swift @@ -0,0 +1,44 @@ +import Foundation +import Postbox +import TelegramCore +import AsyncDisplayKit + +final class InstantPageFeedbackItem: InstantPageItem { + var frame: CGRect + let wantsNode: Bool = true + let medias: [InstantPageMedia] = [] + + init(frame: CGRect) { + self.frame = frame + } + + func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (Int, Int) -> Void) -> (InstantPageNode & ASDisplayNode)? { + return InstantPageFeedbackNode(account: account, strings: strings, theme: theme, openPeer: openPeer) + } + + func matchesAnchor(_ anchor: String) -> Bool { + return false + } + + func matchesNode(_ node: InstantPageNode) -> Bool { + if node is InstantPageFeedbackNode { + return true + } + return false + } + + func distanceThresholdGroup() -> Int? { + return nil + } + + func distanceThresholdWithGroupCount(_ count: Int) -> CGFloat { + return 0.0 + } + + func linkSelectionRects(at point: CGPoint) -> [CGRect] { + return [] + } + + func drawInTile(context: CGContext) { + } +} diff --git a/TelegramUI/InstantPageFeedbackNode.swift b/TelegramUI/InstantPageFeedbackNode.swift new file mode 100644 index 0000000000..e89e64bde2 --- /dev/null +++ b/TelegramUI/InstantPageFeedbackNode.swift @@ -0,0 +1,98 @@ +import Foundation +import AsyncDisplayKit +import Display +import Postbox +import TelegramCore +import SwiftSignalKit + +final class InstantPageFeedbackNode: ASDisplayNode, InstantPageNode { + private let account: Account + private let openPeer: (PeerId) -> Void + + private let highlightedBackgroundNode: ASDisplayNode + private let buttonNode: HighlightableButtonNode + private let labelNode: ASTextNode + + private let resolveDisposable = MetaDisposable() + + init(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openPeer: @escaping (PeerId) -> Void) { + self.account = account + self.openPeer = openPeer + + self.highlightedBackgroundNode = ASDisplayNode() + self.highlightedBackgroundNode.isLayerBacked = true + self.highlightedBackgroundNode.alpha = 0.0 + + self.buttonNode = HighlightableButtonNode() + + self.labelNode = ASTextNode() + self.labelNode.isLayerBacked = true + self.labelNode.maximumNumberOfLines = 2 + + super.init() + + self.addSubnode(self.highlightedBackgroundNode) + self.addSubnode(self.buttonNode) + self.addSubnode(self.labelNode) + + self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) + + self.buttonNode.highligthedChanged = { [weak self] highlighted in + if let strongSelf = self { + if highlighted { + strongSelf.highlightedBackgroundNode.layer.removeAnimation(forKey: "opacity") + strongSelf.highlightedBackgroundNode.alpha = 1.0 + } else { + strongSelf.highlightedBackgroundNode.alpha = 0.0 + strongSelf.highlightedBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2) + } + } + } + + self.update(strings: strings, theme: theme) + } + + deinit { + self.resolveDisposable.dispose() + } + + @objc func buttonPressed() { + self.resolveDisposable.set((resolvePeerByName(account: self.account, name: "previews") |> deliverOnMainQueue).start(next: { peerId in + if let peerId = peerId { + self.openPeer(peerId) + } + })) + } + + override func layout() { + super.layout() + + let size = self.bounds.size + let inset: CGFloat = 15.0 + + self.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: size.width, height: size.height + UIScreenPixel)) + self.buttonNode.frame = CGRect(origin: CGPoint(), size: size) + + let labelSize = self.labelNode.measure(CGSize(width: size.width - inset * 2.0, height: size.height)) + + self.labelNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - labelSize.width) / 2.0), y: floorToScreenPixels((size.height - labelSize.height) / 2.0)), size: labelSize) + } + + func updateIsVisible(_ isVisible: Bool) { + + } + + func transitionNode(media: InstantPageMedia) -> (ASDisplayNode, () -> UIView?)? { + return nil + } + + func updateHiddenMedia(media: InstantPageMedia?) { + + } + + func update(strings: PresentationStrings, theme: InstantPageTheme) { + self.backgroundColor = theme.panelBackgroundColor + self.highlightedBackgroundNode.backgroundColor = theme.panelHighlightedBackgroundColor + self.labelNode.attributedText = NSAttributedString(string: strings.InstantPage_FeedbackButton, font: Font.regular(13.0), textColor: theme.panelSecondaryColor) + } +} diff --git a/TelegramUI/InstantPageGalleryController.swift b/TelegramUI/InstantPageGalleryController.swift index a94e751e9c..bc8a6db005 100644 --- a/TelegramUI/InstantPageGalleryController.swift +++ b/TelegramUI/InstantPageGalleryController.swift @@ -5,6 +5,7 @@ import Postbox import SwiftSignalKit import AsyncDisplayKit import TelegramCore +import SafariServices struct InstantPageGalleryEntryLocation: Equatable { let position: Int32 @@ -19,18 +20,32 @@ struct InstantPageGalleryEntry: Equatable { let index: Int32 let pageId: MediaId let media: InstantPageMedia - let caption: String + let caption: RichText? let location: InstantPageGalleryEntryLocation static func ==(lhs: InstantPageGalleryEntry, rhs: InstantPageGalleryEntry) -> Bool { return lhs.index == rhs.index && lhs.pageId == rhs.pageId && lhs.media == rhs.media && lhs.caption == rhs.caption && lhs.location == rhs.location } - func item(account: Account, webPage: TelegramMediaWebpage, presentationData: PresentationData) -> GalleryItem { + func item(account: Account, webPage: TelegramMediaWebpage, presentationData: PresentationData, openUrl: @escaping (InstantPageUrlItem) -> Void, openUrlOptions: @escaping (InstantPageUrlItem) -> Void) -> GalleryItem { + let styleStack = InstantPageTextStyleStack() + styleStack.push(.fontSize(16.0)) + styleStack.push(.textColor(.white)) + styleStack.push(.markerColor(UIColor(rgb: 0x313131))) + styleStack.push(.fontSerif(false)) + styleStack.push(.lineSpacingFactor(1.0)) + + let attributedString: NSAttributedString + if let caption = self.media.caption { + attributedString = attributedStringForRichText(caption, styleStack: styleStack) + } else { + attributedString = NSAttributedString(string: "") + } + if let image = self.media.media as? TelegramMediaImage { - return InstantImageGalleryItem(account: account, presentationData: presentationData, imageReference: .webPage(webPage: WebpageReference(webPage), media: image), caption: self.caption, location: self.location) + return InstantImageGalleryItem(account: account, presentationData: presentationData, imageReference: .webPage(webPage: WebpageReference(webPage), media: image), caption: attributedString, location: self.location, openUrl: openUrl, openUrlOptions: openUrlOptions) } else if let file = self.media.media as? TelegramMediaFile, file.isVideo { - return UniversalVideoGalleryItem(account: account, presentationData: presentationData, content: NativeVideoContent(id: .instantPage(self.pageId, file.fileId), fileReference: .webPage(webPage: WebpageReference(webPage), media: file)), originData: nil, indexData: GalleryItemIndexData(position: self.location.position, totalCount: self.location.totalCount), contentInfo: .webPage(webPage, file), caption: self.caption) + return UniversalVideoGalleryItem(account: account, presentationData: presentationData, content: NativeVideoContent(id: .instantPage(self.pageId, file.fileId), fileReference: .webPage(webPage: WebpageReference(webPage), media: file)), originData: nil, indexData: GalleryItemIndexData(position: self.location.position, totalCount: self.location.totalCount), contentInfo: .webPage(webPage, file), caption: attributedString, openUrl: { _ in }, openUrlOptions: { _ in }) } else { preconditionFailure() } @@ -77,14 +92,32 @@ class InstantPageGalleryController: ViewController { } private let replaceRootController: (ViewController, ValuePromise?) -> Void + private let baseNavigationController: NavigationController? - init(account: Account, webPage: TelegramMediaWebpage, entries: [InstantPageGalleryEntry], centralIndex: Int, replaceRootController: @escaping (ViewController, ValuePromise?) -> Void) { + private var openUrl: (InstantPageUrlItem) -> Void + private var openUrlOptions: (InstantPageUrlItem) -> Void + + private let resolveUrlDisposable = MetaDisposable() + private let loadWebpageDisposable = MetaDisposable() + + init(account: Account, webPage: TelegramMediaWebpage, entries: [InstantPageGalleryEntry], centralIndex: Int, replaceRootController: @escaping (ViewController, ValuePromise?) -> Void, baseNavigationController: NavigationController?) { self.account = account self.webPage = webPage self.replaceRootController = replaceRootController + self.baseNavigationController = baseNavigationController self.presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } + var openLinkImpl: ((InstantPageUrlItem) -> Void)? + self.openUrl = { url in + openLinkImpl?(url) + } + + var openLinkOptionsImpl: ((InstantPageUrlItem) -> Void)? + self.openUrlOptions = { url in + openLinkOptionsImpl?(url) + } + super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: GalleryController.darkNavigationTheme, strings: NavigationBarStrings(presentationStrings: self.presentationData.strings))) let backItem = UIBarButtonItem(backButtonAppearanceWithTitle: presentationData.strings.Common_Back, target: self, action: #selector(self.donePressed)) @@ -100,7 +133,7 @@ class InstantPageGalleryController: ViewController { strongSelf.centralEntryIndex = centralIndex if strongSelf.isViewLoaded { strongSelf.galleryNode.pager.replaceItems(strongSelf.entries.map({ - $0.item(account: account, webPage: webPage, presentationData: strongSelf.presentationData) + $0.item(account: account, webPage: webPage, presentationData: strongSelf.presentationData, openUrl: strongSelf.openUrl, openUrlOptions: strongSelf.openUrlOptions) }), centralItemIndex: centralIndex, keepFirst: false) let ready = strongSelf.galleryNode.pager.ready() |> timeout(2.0, queue: Queue.mainQueue(), alternate: .single(Void())) |> afterNext { [weak strongSelf] _ in @@ -124,6 +157,88 @@ class InstantPageGalleryController: ViewController { $0.withUpdatedFooterContentNode(footerContentNode) }, transition: .immediate) })) + + openLinkImpl = { [weak self] url in + if let strongSelf = self { + strongSelf.resolveUrlDisposable.set((resolveUrl(account: strongSelf.account, url: url.url) |> deliverOnMainQueue).start(next: { [weak self] result in + if let strongSelf = self { + let navigationController = strongSelf.baseNavigationController + strongSelf.dismiss(forceAway: true) + switch result { + case let .externalUrl(externalUrl): + if let webpageId = url.webpageId { + var anchor: String? + if let anchorRange = externalUrl.range(of: "#") { + anchor = String(externalUrl[anchorRange.upperBound...]) + } + strongSelf.loadWebpageDisposable.set((webpagePreview(account: strongSelf.account, url: externalUrl, webpageId: webpageId) |> deliverOnMainQueue).start(next: { webpage in + if let strongSelf = self, let navigationController = navigationController, let webpage = webpage { + navigationController.pushViewController(InstantPageController(account: strongSelf.account, webPage: webpage, anchor: anchor)) + } + })) + } else { + openExternalUrl(account: strongSelf.account, url: externalUrl, presentationData: strongSelf.account.telegramApplicationContext.currentPresentationData.with { $0 }, applicationContext: strongSelf.account.telegramApplicationContext, navigationController: navigationController, dismissInput: { + self?.view.endEditing(true) + }) + } + default: + openResolvedUrl(result, account: strongSelf.account, navigationController: navigationController, openPeer: { peerId, navigation in + self?.dismiss(forceAway: true) + switch navigation { + case let .chat(_, messageId): + if let navigationController = navigationController { + navigateToChatController(navigationController: navigationController, account: strongSelf.account, chatLocation: .peer(peerId), messageId: messageId) + } + case .info: + let _ = (strongSelf.account.postbox.loadedPeerWithId(peerId) + |> deliverOnMainQueue).start(next: { peer in + if let strongSelf = self, let navigationController = navigationController, let controller = peerInfoController(account: strongSelf.account, peer: peer) { + navigationController.pushViewController(controller) + } + }) + default: + break + } + }, present: { c, _ in + self?.present(c, in: .window(.root)) + }, dismissInput: { + self?.view.endEditing(true) + }) + } + } + })) + } + } + + openLinkOptionsImpl = { [weak self] url in + if let strongSelf = self { + let canOpenIn = availableOpenInOptions(applicationContext: account.telegramApplicationContext, item: .url(url: url.url)).count > 1 + let openText = canOpenIn ? strongSelf.presentationData.strings.Conversation_FileOpenIn : strongSelf.presentationData.strings.Conversation_LinkDialogOpen + let actionSheet = ActionSheetController(presentationTheme: strongSelf.presentationData.theme) + actionSheet.setItemGroups([ActionSheetItemGroup(items: [ + ActionSheetTextItem(title: url.url), + ActionSheetButtonItem(title: openText, color: .accent, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + openLinkImpl?(url) + }), + ActionSheetButtonItem(title: strongSelf.presentationData.strings.ShareMenu_CopyShareLink, color: .accent, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + UIPasteboard.general.string = url.url + }), + ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_AddToReadingList, color: .accent, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + if let link = URL(string: url.url) { + let _ = try? SSReadingList.default()?.addItem(with: link, title: nil, previewText: nil) + } + }) + ]), ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + }) + ])]) + strongSelf.present(actionSheet, in: .window(.root)) + } + } } required init(coder aDecoder: NSCoder) { @@ -132,6 +247,8 @@ class InstantPageGalleryController: ViewController { deinit { self.disposable.dispose() + self.resolveUrlDisposable.dispose() + self.loadWebpageDisposable.dispose() self.centralItemAttributesDisposable.dispose() } @@ -202,7 +319,7 @@ class InstantPageGalleryController: ViewController { } self.galleryNode.pager.replaceItems(self.entries.map({ - $0.item(account: account, webPage: self.webPage, presentationData: self.presentationData) + $0.item(account: account, webPage: self.webPage, presentationData: self.presentationData, openUrl: self.openUrl, openUrlOptions: self.openUrlOptions) }), centralItemIndex: self.centralEntryIndex) self.galleryNode.pager.centralItemIndexUpdated = { [weak self] index in diff --git a/TelegramUI/InstantPageGalleryFooterContentNode.swift b/TelegramUI/InstantPageGalleryFooterContentNode.swift index c52f2a3549..e12b372ade 100644 --- a/TelegramUI/InstantPageGalleryFooterContentNode.swift +++ b/TelegramUI/InstantPageGalleryFooterContentNode.swift @@ -17,9 +17,12 @@ final class InstantPageGalleryFooterContentNode: GalleryFooterContentNode { private var shareMedia: AnyMediaReference? private let actionButton: UIButton - private let textNode: ASTextNode + private let textNode: ImmediateTextNode - private var currentMessageText: String? + private var currentMessageText: NSAttributedString? + + var openUrl: ((InstantPageUrlItem) -> Void)? + var openUrlOptions: ((InstantPageUrlItem) -> Void)? init(account: Account, presentationData: PresentationData) { self.account = account @@ -30,26 +33,47 @@ final class InstantPageGalleryFooterContentNode: GalleryFooterContentNode { self.actionButton.setImage(actionImage, for: [.normal]) - self.textNode = ASTextNode() + self.textNode = ImmediateTextNode() + self.textNode.maximumNumberOfLines = 10 + self.textNode.insets = UIEdgeInsets(top: 8.0, left: 0.0, bottom: 8.0, right: 0.0) + self.textNode.linkHighlightColor = UIColor(white: 1.0, alpha: 0.4) super.init() + self.textNode.highlightAttributeAction = { attributes in + if let _ = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.URL)] { + return NSAttributedStringKey(rawValue: TelegramTextAttributes.URL) + } else { + return nil + } + } + self.textNode.tapAttributeAction = { [weak self] attributes in + if let strongSelf = self, let url = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.URL)] as? InstantPageUrlItem { + strongSelf.openUrl?(url) + } + } + self.textNode.longTapAttributeAction = { [weak self] attributes in + if let strongSelf = self, let url = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.URL)] as? InstantPageUrlItem { + strongSelf.openUrlOptions?(url) + } + } + self.view.addSubview(self.actionButton) self.addSubnode(self.textNode) self.actionButton.addTarget(self, action: #selector(self.actionButtonPressed), for: [.touchUpInside]) } - func setCaption(_ caption: String) { + func setCaption(_ caption: NSAttributedString) { if self.currentMessageText != caption { self.currentMessageText = caption - if caption.isEmpty { + if caption.length == 0 { self.textNode.isHidden = true self.textNode.attributedText = nil } else { self.textNode.isHidden = false - self.textNode.attributedText = NSAttributedString(string: caption, font: textFont, textColor: .white) + self.textNode.attributedText = caption } self.requestLayout?(.immediate) @@ -65,9 +89,9 @@ final class InstantPageGalleryFooterContentNode: GalleryFooterContentNode { var panelHeight: CGFloat = 44.0 + bottomInset + contentInset if !self.textNode.isHidden { let sideInset: CGFloat = leftInset + 8.0 - let topInset: CGFloat = 8.0 - let bottomInset: CGFloat = 8.0 - let textSize = self.textNode.measure(CGSize(width: width - sideInset * 2.0, height: CGFloat.greatestFiniteMagnitude)) + let topInset: CGFloat = 0.0 + let bottomInset: CGFloat = 0.0 + let textSize = self.textNode.updateLayout(CGSize(width: width - sideInset * 2.0, height: CGFloat.greatestFiniteMagnitude)) panelHeight += textSize.height + topInset + bottomInset transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: sideInset, y: topInset), size: textSize)) } diff --git a/TelegramUI/InstantPageImageItem.swift b/TelegramUI/InstantPageImageItem.swift index fd85bb955d..3def4e10ae 100644 --- a/TelegramUI/InstantPageImageItem.swift +++ b/TelegramUI/InstantPageImageItem.swift @@ -3,12 +3,23 @@ import Postbox import TelegramCore import AsyncDisplayKit +protocol InstantPageImageAttribute { +} + +struct InstantPageMapAttribute: InstantPageImageAttribute { + let zoom: Int32 + let dimensions: CGSize +} + final class InstantPageImageItem: InstantPageItem { var frame: CGRect let webPage: TelegramMediaWebpage + let url: InstantPageUrlItem? let media: InstantPageMedia + let attributes: [InstantPageImageAttribute] + var medias: [InstantPageMedia] { return [self.media] } @@ -19,17 +30,19 @@ final class InstantPageImageItem: InstantPageItem { let wantsNode: Bool = true - init(frame: CGRect, webPage: TelegramMediaWebpage, media: InstantPageMedia, interactive: Bool, roundCorners: Bool, fit: Bool) { + init(frame: CGRect, webPage: TelegramMediaWebpage, media: InstantPageMedia, attributes: [InstantPageImageAttribute] = [], url: InstantPageUrlItem? = nil, interactive: Bool, roundCorners: Bool, fit: Bool) { self.frame = frame self.webPage = webPage self.media = media + self.attributes = attributes + self.url = url self.interactive = interactive self.roundCorners = roundCorners self.fit = fit } - func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void) -> (InstantPageNode & ASDisplayNode)? { - return InstantPageImageNode(account: account, webPage: self.webPage, media: self.media, interactive: self.interactive, roundCorners: self.roundCorners, fit: self.fit, openMedia: openMedia) + func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (Int, Int) -> Void) -> (InstantPageNode & ASDisplayNode)? { + return InstantPageImageNode(account: account, webPage: self.webPage, media: self.media, attributes: self.attributes, url: self.url, interactive: self.interactive, roundCorners: self.roundCorners, fit: self.fit, openMedia: openMedia, openUrl: openUrl) } func matchesAnchor(_ anchor: String) -> Bool { diff --git a/TelegramUI/InstantPageImageNode.swift b/TelegramUI/InstantPageImageNode.swift index 527c88096e..2d780cec27 100644 --- a/TelegramUI/InstantPageImageNode.swift +++ b/TelegramUI/InstantPageImageNode.swift @@ -8,26 +8,34 @@ import SwiftSignalKit final class InstantPageImageNode: ASDisplayNode, InstantPageNode { private let account: Account let media: InstantPageMedia + let attributes: [InstantPageImageAttribute] + let url: InstantPageUrlItem? private let interactive: Bool private let roundCorners: Bool private let fit: Bool private let openMedia: (InstantPageMedia) -> Void + private let openUrl: (InstantPageUrlItem) -> Void private let imageNode: TransformImageNode + private let pinNode: ChatMessageLiveLocationPositionNode private var currentSize: CGSize? private var fetchedDisposable = MetaDisposable() - init(account: Account, webPage: TelegramMediaWebpage, media: InstantPageMedia, interactive: Bool, roundCorners: Bool, fit: Bool, openMedia: @escaping (InstantPageMedia) -> Void) { + init(account: Account, webPage: TelegramMediaWebpage, media: InstantPageMedia, attributes: [InstantPageImageAttribute], url: InstantPageUrlItem? = nil, interactive: Bool, roundCorners: Bool, fit: Bool, openMedia: @escaping (InstantPageMedia) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void = { _ in }) { self.account = account self.media = media + self.attributes = attributes + self.url = url self.interactive = interactive self.roundCorners = roundCorners self.fit = fit self.openMedia = openMedia + self.openUrl = openUrl self.imageNode = TransformImageNode() + self.pinNode = ChatMessageLiveLocationPositionNode() super.init() @@ -39,7 +47,26 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { self.fetchedDisposable.set(chatMessagePhotoInteractiveFetched(account: account, photoReference: imageReference, storeToDownloadsPeerType: nil).start()) } else if let file = media.media as? TelegramMediaFile { let fileReference = FileMediaReference.webPage(webPage: WebpageReference(webPage), media: file) - self.imageNode.setSignal(chatMessageVideo(postbox: account.postbox, videoReference: fileReference)) + if file.mimeType.hasPrefix("image/") { + _ = freeMediaFileInteractiveFetched(account: account, fileReference: fileReference).start() + self.imageNode.setSignal(chatMessageImageFile(account: account, fileReference: fileReference, thumbnail: false, fetched: true)) + } else { + self.imageNode.setSignal(chatMessageVideo(postbox: account.postbox, videoReference: fileReference)) + } + } else if let map = media.media as? TelegramMediaMap { + self.addSubnode(self.pinNode) + + var zoom: Int32 = 12 + var dimensions = CGSize(width: 200, height: 100) + for attribute in self.attributes { + if let mapAttribute = attribute as? InstantPageMapAttribute { + zoom = mapAttribute.zoom + dimensions = mapAttribute.dimensions + break + } + } + let resource = MapSnapshotMediaResource(latitude: map.latitude, longitude: map.longitude, width: Int32(dimensions.width), height: Int32(dimensions.height)) + self.imageNode.setSignal(chatMapSnapshotImage(account: account, resource: resource)) } } @@ -75,10 +102,33 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { let imageSize = largest.dimensions.aspectFilled(size) let boundingSize = size let radius: CGFloat = self.roundCorners ? floor(min(imageSize.width, imageSize.height) / 2.0) : 0.0 - let makeLayout = self.imageNode.asyncLayout() let apply = makeLayout(TransformImageArguments(corners: ImageCorners(radius: radius), imageSize: imageSize, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets())) apply() + } else if let file = self.media.media as? TelegramMediaFile, let dimensions = file.dimensions { + let imageSize = dimensions.aspectFilled(size) + let boundingSize = size + let makeLayout = self.imageNode.asyncLayout() + let apply = makeLayout(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets())) + apply() + } else if self.media.media is TelegramMediaMap { + for attribute in self.attributes { + if let mapAttribute = attribute as? InstantPageMapAttribute { + let imageSize = mapAttribute.dimensions.aspectFilled(size) + let boundingSize = size + let radius: CGFloat = self.roundCorners ? floor(min(imageSize.width, imageSize.height) / 2.0) : 0.0 + let makeLayout = self.imageNode.asyncLayout() + let apply = makeLayout(TransformImageArguments(corners: ImageCorners(radius: radius), imageSize: imageSize, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets())) + apply() + break + } + } + + let makePinLayout = self.pinNode.asyncLayout() + let theme = self.account.telegramApplicationContext.currentPresentationData.with { $0 }.theme + let (pinSize, pinApply) = makePinLayout(self.account, theme, nil, false) + self.pinNode.frame = CGRect(origin: CGPoint(x: floor((size.width - pinSize.width) / 2.0), y: floor(size.height * 0.5 - 10.0 - pinSize.height / 2.0)), size: pinSize) + pinApply() } } } @@ -100,7 +150,11 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { @objc func tapGesture(_ recognizer: UITapGestureRecognizer) { if case .ended = recognizer.state { - self.openMedia(self.media) + if let url = self.url { + self.openUrl(url) + } else { + self.openMedia(self.media) + } } } } diff --git a/TelegramUI/InstantPageItem.swift b/TelegramUI/InstantPageItem.swift index e3fc7e6253..4942e5da8e 100644 --- a/TelegramUI/InstantPageItem.swift +++ b/TelegramUI/InstantPageItem.swift @@ -10,7 +10,7 @@ protocol InstantPageItem { func matchesAnchor(_ anchor: String) -> Bool func drawInTile(context: CGContext) - func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void) -> (InstantPageNode & ASDisplayNode)? + func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (Int, Int) -> Void) -> (InstantPageNode & ASDisplayNode)? func matchesNode(_ node: InstantPageNode) -> Bool func linkSelectionRects(at point: CGPoint) -> [CGRect] diff --git a/TelegramUI/InstantPageLayout.swift b/TelegramUI/InstantPageLayout.swift index a38f2e5c4f..cce3a49ab6 100644 --- a/TelegramUI/InstantPageLayout.swift +++ b/TelegramUI/InstantPageLayout.swift @@ -27,6 +27,7 @@ final class InstantPageLayout { private func setupStyleStack(_ stack: InstantPageTextStyleStack, theme: InstantPageTheme, category: InstantPageTextCategoryType, link: Bool) { let attributes = theme.textCategories.attributes(type: category, link: link) stack.push(.textColor(attributes.color)) + stack.push(.markerColor(theme.markerColor)) switch attributes.font.style { case .sans: stack.push(.fontSerif(false)) @@ -40,22 +41,56 @@ private func setupStyleStack(_ stack: InstantPageTextStyleStack, theme: InstantP } } -func layoutInstantPageBlock(webpage: TelegramMediaWebpage, block: InstantPageBlock, boundingWidth: CGFloat, horizontalInset: CGFloat, safeInset: CGFloat, isCover: Bool, previousItems: [InstantPageItem], fillToWidthAndHeight: Bool, media: [MediaId: Media], mediaIndexCounter: inout Int, embedIndexCounter: inout Int, theme: InstantPageTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat) -> InstantPageLayout { +func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: InstantPageBlock, boundingWidth: CGFloat, horizontalInset: CGFloat, safeInset: CGFloat, isCover: Bool, previousItems: [InstantPageItem], fillToWidthAndHeight: Bool, media: [MediaId: Media], mediaIndexCounter: inout Int, embedIndexCounter: inout Int, theme: InstantPageTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, webEmbedHeights: [Int : Int] = [:]) -> InstantPageLayout { + + let layoutCaption: (InstantPageCaption, CGSize) -> ([InstantPageItem], CGSize) = { caption, contentSize in + var items: [InstantPageItem] = [] + var offset = contentSize.height + var contentSize = CGSize() + if case .empty = caption.text { + } else { + contentSize.height += 14.0 + offset += 14.0 + let styleStack = InstantPageTextStyleStack() + setupStyleStack(styleStack, theme: theme, category: .caption, link: false) + let (captionItems, captionContentSize) = layoutTextItemWithString(attributedStringForRichText(caption.text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0, offset: CGPoint(x: horizontalInset, y: offset), media: media, webpage: webpage) + contentSize.height += captionContentSize.height + offset += captionContentSize.height + items.append(contentsOf: captionItems) + } + + if case .empty = caption.credit { + } else { + if case .empty = caption.text { + contentSize.height += 14.0 + offset += 14.0 + } else { + contentSize.height += 10.0 + offset += 10.0 + } + let styleStack = InstantPageTextStyleStack() + setupStyleStack(styleStack, theme: theme, category: .caption, link: false) + let (captionItems, captionContentSize) = layoutTextItemWithString(attributedStringForRichText(caption.credit, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0, offset: CGPoint(x: horizontalInset, y: offset), media: media, webpage: webpage) + contentSize.height += captionContentSize.height + offset += captionContentSize.height + items.append(contentsOf: captionItems) + } + return (items, contentSize) + } + switch block { case let .cover(block): - return layoutInstantPageBlock(webpage: webpage, block: block, boundingWidth: boundingWidth, horizontalInset: horizontalInset, safeInset: safeInset, isCover: true, previousItems:previousItems, fillToWidthAndHeight: fillToWidthAndHeight, media: media, mediaIndexCounter: &mediaIndexCounter, embedIndexCounter: &embedIndexCounter, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat) + return layoutInstantPageBlock(webpage: webpage, rtl: rtl, block: block, boundingWidth: boundingWidth, horizontalInset: horizontalInset, safeInset: safeInset, isCover: true, previousItems:previousItems, fillToWidthAndHeight: fillToWidthAndHeight, media: media, mediaIndexCounter: &mediaIndexCounter, embedIndexCounter: &embedIndexCounter, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, webEmbedHeights: webEmbedHeights) case let .title(text): let styleStack = InstantPageTextStyleStack() setupStyleStack(styleStack, theme: theme, category: .header, link: false) - let item = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0) - item.frame = item.frame.offsetBy(dx: horizontalInset, dy: 0.0) - return InstantPageLayout(origin: CGPoint(), contentSize: item.frame.size, items: [item]) + let (items, contentSize) = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0, offset: CGPoint(x: horizontalInset, y: 0.0), media: media, webpage: webpage) + return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) case let .subtitle(text): let styleStack = InstantPageTextStyleStack() setupStyleStack(styleStack, theme: theme, category: .subheader, link: false) - let item = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0) - item.frame = item.frame.offsetBy(dx: horizontalInset, dy: 0.0) - return InstantPageLayout(origin: CGPoint(), contentSize: item.frame.size, items: [item]) + let (items, contentSize) = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0, offset: CGPoint(x: horizontalInset, y: 0.0), media: media, webpage: webpage) + return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) case let .authorDate(author: author, date: date): let styleStack = InstantPageTextStyleStack() setupStyleStack(styleStack, theme: theme, category: .caption, link: false) @@ -100,49 +135,54 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, block: InstantPageBlo } } if let text = text { - let item = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0) - item.frame = item.frame.offsetBy(dx: horizontalInset, dy: 0.0) - - if let previousItem = previousItems.last as? InstantPageTextItem, previousItem.containsRTL { - item.alignment = .right + let (items, contentSize) = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0, offset: CGPoint(x: horizontalInset, y: 0.0), media: media, webpage: webpage) + if let textItem = items.first as? InstantPageTextItem { + var previousItemHasRTL = false + if let previousItem = previousItems.last as? InstantPageTextItem, previousItem.containsRTL { + previousItemHasRTL = true + } + if rtl || previousItemHasRTL { + textItem.alignment = .right + } } - - return InstantPageLayout(origin: CGPoint(), contentSize: item.frame.size, items: [item]) + return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) } else { return InstantPageLayout(origin: CGPoint(), contentSize: CGSize(), items: []) } + case let .kicker(text): + let styleStack = InstantPageTextStyleStack() + setupStyleStack(styleStack, theme: theme, category: .kicker, link: false) + let (items, contentSize) = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0, offset: CGPoint(x: horizontalInset, y: 0.0), media: media, webpage: webpage) + return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) case let .header(text): let styleStack = InstantPageTextStyleStack() setupStyleStack(styleStack, theme: theme, category: .header, link: false) - let item = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0) - item.frame = item.frame.offsetBy(dx: horizontalInset, dy: 0.0) - return InstantPageLayout(origin: CGPoint(), contentSize: item.frame.size, items: [item]) + let (items, contentSize) = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0, offset: CGPoint(x: horizontalInset, y: 0.0), media: media, webpage: webpage) + return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) case let .subheader(text): let styleStack = InstantPageTextStyleStack() setupStyleStack(styleStack, theme: theme, category: .subheader, link: false) - let item = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0) - item.frame = item.frame.offsetBy(dx: horizontalInset, dy: 0.0) - return InstantPageLayout(origin: CGPoint(), contentSize: item.frame.size, items: [item]) + let (items, contentSize) = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0, offset: CGPoint(x: horizontalInset, y: 0.0), media: media, webpage: webpage) + return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) case let .paragraph(text): let styleStack = InstantPageTextStyleStack() setupStyleStack(styleStack, theme: theme, category: .paragraph, link: false) - let item = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0) - item.frame = item.frame.offsetBy(dx: horizontalInset, dy: 0.0) - return InstantPageLayout(origin: CGPoint(), contentSize: item.frame.size, items: [item]) + let (items, contentSize) = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0, offset: CGPoint(x: horizontalInset, y: 0.0), media: media, webpage: webpage) + return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) case let .preformatted(text): let styleStack = InstantPageTextStyleStack() setupStyleStack(styleStack, theme: theme, category: .paragraph, link: false) let backgroundInset: CGFloat = 14.0 - let item = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0 - backgroundInset * 2.0) - item.frame = item.frame.offsetBy(dx: horizontalInset, dy: backgroundInset) - let backgroundItem = InstantPageShapeItem(frame: CGRect(origin: CGPoint(), size: CGSize(width: boundingWidth, height: item.frame.size.height + backgroundInset * 2.0)), shapeFrame: CGRect(origin: CGPoint(), size: CGSize(width: boundingWidth, height: item.frame.size.height + backgroundInset * 2.0)), shape: .rect, color: theme.codeBlockBackgroundColor) - return InstantPageLayout(origin: CGPoint(), contentSize: CGSize(width: boundingWidth, height: item.frame.size.height + backgroundInset * 2.0), items: [backgroundItem, item]) + let (items, contentSize) = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0 - backgroundInset * 2.0, offset: CGPoint(x: horizontalInset, y: backgroundInset), media: media, webpage: webpage) + let backgroundItem = InstantPageShapeItem(frame: CGRect(origin: CGPoint(), size: CGSize(width: boundingWidth, height: contentSize.height + backgroundInset * 2.0)), shapeFrame: CGRect(origin: CGPoint(), size: CGSize(width: boundingWidth, height: contentSize.height + backgroundInset * 2.0)), shape: .rect, color: theme.codeBlockBackgroundColor) + var allItems: [InstantPageItem] = [backgroundItem] + allItems.append(contentsOf: items) + return InstantPageLayout(origin: CGPoint(), contentSize: CGSize(width: boundingWidth, height: contentSize.height + backgroundInset * 2.0), items: allItems) case let .footer(text): let styleStack = InstantPageTextStyleStack() setupStyleStack(styleStack, theme: theme, category: .caption, link: false) - let item = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0) - item.frame = item.frame.offsetBy(dx: horizontalInset, dy: 0.0) - return InstantPageLayout(origin: CGPoint(), contentSize: item.frame.size, items: [item]) + let (items, contentSize) = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0, offset: CGPoint(x: horizontalInset, y: 0.0), media: media, webpage: webpage) + return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) case .divider: let lineWidth = floor(boundingWidth / 2.0) let shapeItem = InstantPageShapeItem(frame: CGRect(origin: CGPoint(x: floor((boundingWidth - lineWidth) / 2.0), y: 0.0), size: CGSize(width: lineWidth, height: 1.0)), shapeFrame: CGRect(origin: CGPoint(), size: CGSize(width: lineWidth, height: 1.0)), shape: .rect, color: theme.textCategories.caption.color) @@ -152,36 +192,104 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, block: InstantPageBlo var maxIndexWidth: CGFloat = 0.0 var listItems: [InstantPageItem] = [] var indexItems: [InstantPageItem] = [] + + var hasNums = false + if ordered { + for item in contentItems { + if let num = item.num, !num.isEmpty { + hasNums = true + break + } + } + } + for i in 0 ..< contentItems.count { + let item = contentItems[i] if ordered { let styleStack = InstantPageTextStyleStack() setupStyleStack(styleStack, theme: theme, category: .paragraph, link: false) - let textItem = layoutTextItemWithString(attributedStringForRichText(.plain("\(i + 1)."), styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0) - if let line = textItem.lines.first { - maxIndexWidth = max(maxIndexWidth, line.frame.size.width) + let value: String + if hasNums { + if let num = item.num { + value = "\(num)." + } else { + value = " " + } + } else { + value = "\(i + 1)." } - indexItems.append(textItem) + let (textItems, _) = layoutTextItemWithString(attributedStringForRichText(.plain(value), styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0, offset: CGPoint()) + if let textItem = textItems.first as? InstantPageTextItem, let line = textItem.lines.first { + textItem.selectable = false + maxIndexWidth = max(maxIndexWidth, line.frame.width) + } + indexItems.append(textItems.first!) } else { let shapeItem = InstantPageShapeItem(frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: 6.0, height: 12.0)), shapeFrame: CGRect(origin: CGPoint(x: 0.0, y: 3.0), size: CGSize(width: 6.0, height: 6.0)), shape: .ellipse, color: theme.textCategories.paragraph.color) indexItems.append(shapeItem) } } - let indexSpacing: CGFloat = ordered ? 7.0 : 20.0 - for i in 0 ..< contentItems.count { + let indexSpacing: CGFloat = ordered ? 12.0 : 20.0 + for (i, item) in contentItems.enumerated() { if (i != 0) { - contentSize.height += 20.0 + contentSize.height += 18.0 } let styleStack = InstantPageTextStyleStack() setupStyleStack(styleStack, theme: theme, category: .paragraph, link: false) - let textItem = layoutTextItemWithString(attributedStringForRichText(contentItems[i], styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0 - indexSpacing - maxIndexWidth) - textItem.frame = textItem.frame.offsetBy(dx: horizontalInset + indexSpacing + maxIndexWidth, dy: contentSize.height) - - contentSize.height += textItem.frame.size.height - let itemFrame = indexItems[i].frame.offsetBy(dx: horizontalInset, dy: textItem.frame.origin.y) - indexItems[i].frame = itemFrame - listItems.append(indexItems[i]) - listItems.append(textItem) + switch item { + case let .text(text, _): + let (textItems, textItemSize) = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0 - indexSpacing - maxIndexWidth, offset: CGPoint(x: horizontalInset + indexSpacing + maxIndexWidth, y: contentSize.height), media: media, webpage: webpage) + + contentSize.height += textItemSize.height + var indexItem = indexItems[i] + var itemFrame = indexItem.frame + + var lineMidY: CGFloat = 0.0 + var textItem = textItems.first! + if let textItem = textItem as? InstantPageTextItem, let line = textItem.lines.first { + lineMidY = textItem.frame.minY + line.frame.midY + } else { + lineMidY = textItem.frame.midY + } + + if let textIndexItem = indexItem as? InstantPageTextItem, let line = textIndexItem.lines.first { + itemFrame = itemFrame.offsetBy(dx: horizontalInset + maxIndexWidth - line.frame.width, dy: floorToScreenPixels(lineMidY - (itemFrame.height / 2.0))) + } else { + itemFrame = itemFrame.offsetBy(dx: horizontalInset, dy: floorToScreenPixels(lineMidY - itemFrame.height / 2.0)) + } + indexItems[i].frame = itemFrame + listItems.append(indexItems[i]) + listItems.append(contentsOf: textItems) + case let .blocks(blocks, _): + var previousBlock: InstantPageBlock? + var originY: CGFloat = 0.0 + for subBlock in blocks { + let subLayout = layoutInstantPageBlock(webpage: webpage, rtl: rtl, block: subBlock, boundingWidth: boundingWidth - horizontalInset * 2.0 - indexSpacing - maxIndexWidth, horizontalInset: 0.0, safeInset: 0.0, isCover: false, previousItems: listItems, fillToWidthAndHeight: false, media: media, mediaIndexCounter: &mediaIndexCounter, embedIndexCounter: &embedIndexCounter, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, webEmbedHeights: webEmbedHeights) + + let spacing: CGFloat = previousBlock != nil ? spacingBetweenBlocks(upper: previousBlock, lower: subBlock) : 0.0 + let blockItems = subLayout.flattenedItemsWithOrigin(CGPoint(x: horizontalInset + indexSpacing + maxIndexWidth, y: contentSize.height + spacing)) + if previousBlock == nil { + originY = contentSize.height + spacing + } + listItems.append(contentsOf: blockItems) + contentSize.height += subLayout.contentSize.height + spacing + previousBlock = subBlock + } + var indexItem = indexItems[i] + var indexItemFrame = indexItem.frame + if let textIndexItem = indexItem as? InstantPageTextItem, let line = textIndexItem.lines.first { + indexItemFrame = indexItemFrame.offsetBy(dx: horizontalInset + maxIndexWidth - line.frame.width, dy: originY) + } else { + indexItemFrame = indexItemFrame.offsetBy(dx: horizontalInset, dy: originY) + } + indexItems[i].frame = indexItemFrame + listItems.append(indexItems[i]) + break + + default: + break + } } return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: listItems) case let .blockQuote(text, caption): @@ -195,11 +303,10 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, block: InstantPageBlo setupStyleStack(styleStack, theme: theme, category: .paragraph, link: false) styleStack.push(.italic) - let textItem = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0 - lineInset) - textItem.frame = textItem.frame.offsetBy(dx: horizontalInset + lineInset, dy: contentSize.height) + let (textItems, textContentSize) = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0 - lineInset, offset: CGPoint(x: horizontalInset + lineInset, y: contentSize.height), media: media, webpage: webpage) - contentSize.height += textItem.frame.size.height - items.append(textItem) + contentSize.height += textContentSize.height + items.append(contentsOf: textItems) if case .empty = caption { } else { @@ -208,11 +315,10 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, block: InstantPageBlo let styleStack = InstantPageTextStyleStack() setupStyleStack(styleStack, theme: theme, category: .caption, link: false) - let captionItem = layoutTextItemWithString(attributedStringForRichText(caption, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0 - lineInset) - captionItem.frame = captionItem.frame.offsetBy(dx: horizontalInset + lineInset, dy: contentSize.height) + let (captionItems, captionContentSize) = layoutTextItemWithString(attributedStringForRichText(caption, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0 - lineInset, offset: CGPoint(x: horizontalInset + lineInset, y: contentSize.height), media: media, webpage: webpage) - contentSize.height += captionItem.frame.size.height - items.append(captionItem) + contentSize.height += captionContentSize.height + items.append(contentsOf: captionItems) } contentSize.height += verticalInset @@ -231,12 +337,14 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, block: InstantPageBlo setupStyleStack(styleStack, theme: theme, category: .paragraph, link: false) styleStack.push(.italic) - let textItem = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0) - textItem.frame = textItem.frame.offsetBy(dx: floor(boundingWidth - textItem.frame.size.width) / 2.0, dy: contentSize.height) - textItem.alignment = .center + let (textItems, textContentSize) = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0, offset: CGPoint(x: 0.0, y: contentSize.height), media: media, webpage: webpage) + if let textItem = textItems.first as? InstantPageTextItem { + textItem.frame = textItem.frame.offsetBy(dx: floor(boundingWidth - textItem.frame.width) / 2.0, dy: contentSize.height) + textItem.alignment = .center + } - contentSize.height += textItem.frame.size.height - items.append(textItem) + contentSize.height += textContentSize.height + items.append(contentsOf: textItems) if case .empty = caption { } else { @@ -245,16 +353,18 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, block: InstantPageBlo let styleStack = InstantPageTextStyleStack() setupStyleStack(styleStack, theme: theme, category: .caption, link: false) - let captionItem = layoutTextItemWithString(attributedStringForRichText(caption, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0) - captionItem.frame = captionItem.frame.offsetBy(dx: floor(boundingWidth - captionItem.frame.size.width) / 2.0, dy: contentSize.height) - captionItem.alignment = .center + let (captionItems, captionContentSize) = layoutTextItemWithString(attributedStringForRichText(caption, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0, offset: CGPoint(x: 0.0, y: contentSize.height), media: media, webpage: webpage) + if let textItem = captionItems.first as? InstantPageTextItem { + textItem.frame = textItem.frame.offsetBy(dx: floor(boundingWidth - textItem.frame.width) / 2.0, dy: contentSize.height) + textItem.alignment = .center + } - contentSize.height += captionItem.frame.size.height - items.append(captionItem) + contentSize.height += captionContentSize.height + items.append(contentsOf: captionItems) } contentSize.height += verticalInset return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) - case let .image(id, caption): + case let .image(id, caption, url, webpageId): if let image = media[id] as? TelegramMediaImage, let largest = largestImageRepresentation(image.representations) { let imageSize = largest.dimensions var filledSize = imageSize.aspectFitted(CGSize(width: boundingWidth - safeInset * 2.0, height: 1200.0)) @@ -274,26 +384,20 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, block: InstantPageBlo var contentSize = CGSize(width: boundingWidth - safeInset * 2.0, height: 0.0) var items: [InstantPageItem] = [] - let mediaItem = InstantPageImageItem(frame: CGRect(origin: CGPoint(x: floor((boundingWidth - filledSize.width) / 2.0), y: 0.0), size: filledSize), webPage: webpage, media: InstantPageMedia(index: mediaIndex, media: image, caption: caption.plainText), interactive: true, roundCorners: false, fit: false) + var urlItem: InstantPageUrlItem? + if let url = url { + urlItem = InstantPageUrlItem(url: url, webpageId: webpageId) + } + + let mediaItem = InstantPageImageItem(frame: CGRect(origin: CGPoint(x: floor((boundingWidth - filledSize.width) / 2.0), y: 0.0), size: filledSize), webPage: webpage, media: InstantPageMedia(index: mediaIndex, media: image, caption: caption.text), url: urlItem, interactive: true, roundCorners: false, fit: false) items.append(mediaItem) contentSize.height += filledSize.height - if case .empty = caption { - } else { - contentSize.height += 10.0 - - let styleStack = InstantPageTextStyleStack() - setupStyleStack(styleStack, theme: theme, category: .caption, link: false) - - let captionItem = layoutTextItemWithString(attributedStringForRichText(caption, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0) - captionItem.frame = captionItem.frame.offsetBy(dx: floor(boundingWidth - captionItem.frame.size.width) / 2.0, dy: contentSize.height) - captionItem.alignment = .center - - contentSize.height += captionItem.frame.size.height - items.append(captionItem) - } - + let (captionItems, captionSize) = layoutCaption(caption, contentSize) + items.append(contentsOf: captionItems) + contentSize.height += captionSize.height + return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) } else { return InstantPageLayout(origin: CGPoint(), contentSize: CGSize(), items: []) @@ -319,30 +423,19 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, block: InstantPageBlo var items: [InstantPageItem] = [] if autoplay { - let mediaItem = InstantPagePlayableVideoItem(frame: CGRect(origin: CGPoint(x: floor((boundingWidth - filledSize.width) / 2.0), y: 0.0), size: filledSize), webPage: webpage, media: InstantPageMedia(index: mediaIndex, media: file, caption: caption.plainText), interactive: true) + let mediaItem = InstantPagePlayableVideoItem(frame: CGRect(origin: CGPoint(x: floor((boundingWidth - filledSize.width) / 2.0), y: 0.0), size: filledSize), webPage: webpage, media: InstantPageMedia(index: mediaIndex, media: file, caption: caption.text), interactive: true) items.append(mediaItem) } else { - let mediaItem = InstantPageImageItem(frame: CGRect(origin: CGPoint(x: floor((boundingWidth - filledSize.width) / 2.0), y: 0.0), size: filledSize), webPage: webpage, media: InstantPageMedia(index: mediaIndex, media: file, caption: caption.plainText), interactive: true, roundCorners: false, fit: false) + let mediaItem = InstantPageImageItem(frame: CGRect(origin: CGPoint(x: floor((boundingWidth - filledSize.width) / 2.0), y: 0.0), size: filledSize), webPage: webpage, media: InstantPageMedia(index: mediaIndex, media: file, caption: caption.text), url: nil, interactive: true, roundCorners: false, fit: false) items.append(mediaItem) } contentSize.height += filledSize.height - if case .empty = caption { - } else { - contentSize.height += 10.0 - - let styleStack = InstantPageTextStyleStack() - setupStyleStack(styleStack, theme: theme, category: .caption, link: false) - - let captionItem = layoutTextItemWithString(attributedStringForRichText(caption, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0) - captionItem.frame = captionItem.frame.offsetBy(dx: floor(boundingWidth - captionItem.frame.size.width) / 2.0, dy: contentSize.height) - captionItem.alignment = .center - - contentSize.height += captionItem.frame.size.height - items.append(captionItem) - } + let (captionItems, captionSize) = layoutCaption(caption, contentSize) + items.append(contentsOf: captionItems) + contentSize.height += captionSize.height return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) } else { @@ -361,27 +454,16 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, block: InstantPageBlo nextItemOrigin.x = 0.0 nextItemOrigin.y += itemSize + spacing } - let subLayout = layoutInstantPageBlock(webpage: webpage, block: subItem, boundingWidth: itemSize, horizontalInset: 0.0, safeInset: 0.0, isCover: false, previousItems: items, fillToWidthAndHeight: true, media: media, mediaIndexCounter: &mediaIndexCounter, embedIndexCounter: &embedIndexCounter, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat) + let subLayout = layoutInstantPageBlock(webpage: webpage, rtl: rtl, block: subItem, boundingWidth: itemSize, horizontalInset: 0.0, safeInset: 0.0, isCover: false, previousItems: items, fillToWidthAndHeight: true, media: media, mediaIndexCounter: &mediaIndexCounter, embedIndexCounter: &embedIndexCounter, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, webEmbedHeights: webEmbedHeights) items.append(contentsOf: subLayout.flattenedItemsWithOrigin(nextItemOrigin)) nextItemOrigin.x += itemSize + spacing } var contentSize = CGSize(width: boundingWidth - safeInset * 2.0, height: nextItemOrigin.y + itemSize) - if case .empty = caption { - } else { - contentSize.height += 10.0 - - let styleStack = InstantPageTextStyleStack() - setupStyleStack(styleStack, theme: theme, category: .caption, link: false) - - let captionItem = layoutTextItemWithString(attributedStringForRichText(caption, styleStack: styleStack), boundingWidth: boundingWidth - safeInset * 2.0 - horizontalInset * 2.0) - captionItem.frame = captionItem.frame.offsetBy(dx: floor(boundingWidth - captionItem.frame.size.width) / 2.0, dy: contentSize.height) - captionItem.alignment = .center - - contentSize.height += captionItem.frame.size.height - items.append(captionItem) - } + let (captionItems, captionSize) = layoutCaption(caption, contentSize) + items.append(contentsOf: captionItems) + contentSize.height += captionSize.height return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) case let .postEmbed(url, webpageId, avatarId, author, date, blocks, caption): @@ -399,7 +481,7 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, block: InstantPageBlo if !author.isEmpty { let avatar: TelegramMediaImage? = avatarId.flatMap { media[$0] as? TelegramMediaImage } if let avatar = avatar { - let avatarItem = InstantPageImageItem(frame: CGRect(origin: CGPoint(x: horizontalInset + lineInset + 1.0, y: contentSize.height - 2.0), size: CGSize(width: 50.0, height: 50.0)), webPage: webpage, media: InstantPageMedia(index: -1, media: avatar, caption: ""), interactive: false, roundCorners: true, fit: false) + let avatarItem = InstantPageImageItem(frame: CGRect(origin: CGPoint(x: horizontalInset + lineInset + 1.0, y: contentSize.height - 2.0), size: CGSize(width: 50.0, height: 50.0)), webPage: webpage, media: InstantPageMedia(index: -1, media: avatar, caption: nil), url: nil, interactive: false, roundCorners: true, fit: false) items.append(avatarItem) avatarInset += 62.0 @@ -413,11 +495,10 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, block: InstantPageBlo setupStyleStack(styleStack, theme: theme, category: .paragraph, link: false) styleStack.push(.bold) - let textItem = layoutTextItemWithString(attributedStringForRichText(.plain(author), styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0 - lineInset - avatarInset) - textItem.frame = textItem.frame.offsetBy(dx: horizontalInset + lineInset + avatarInset, dy: contentSize.height + avatarVerticalInset) - items.append(textItem) + let (textItems, textContentSize) = layoutTextItemWithString(attributedStringForRichText(.plain(author), styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0 - lineInset - avatarInset, offset: CGPoint(x: horizontalInset + lineInset + avatarInset, y: contentSize.height + avatarVerticalInset), media: media, webpage: webpage) + items.append(contentsOf: textItems) - contentSize.height += textItem.frame.size.height + avatarVerticalInset + contentSize.height += textContentSize.height + avatarVerticalInset } if date != 0 { if items.count != 0 { @@ -429,11 +510,10 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, block: InstantPageBlo let styleStack = InstantPageTextStyleStack() setupStyleStack(styleStack, theme: theme, category: .caption, link: false) - let textItem = layoutTextItemWithString(attributedStringForRichText(.plain(dateString), styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0 - lineInset - avatarInset) - textItem.frame = textItem.frame.offsetBy(dx: horizontalInset + lineInset + avatarInset, dy: contentSize.height) - items.append(textItem) + let (textItems, textContentSize) = layoutTextItemWithString(attributedStringForRichText(.plain(dateString), styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0 - lineInset - avatarInset, offset: CGPoint(x: horizontalInset + lineInset + avatarInset, y: contentSize.height), media: media, webpage: webpage) + items.append(contentsOf: textItems) - contentSize.height += textItem.frame.size.height + contentSize.height += textContentSize.height } if items.count != 0 { @@ -442,7 +522,7 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, block: InstantPageBlo var previousBlock: InstantPageBlock? for subBlock in blocks { - let subLayout = layoutInstantPageBlock(webpage: webpage, block: subBlock, boundingWidth: boundingWidth - horizontalInset * 2.0 - lineInset, horizontalInset: 0.0, safeInset: 0.0, isCover: false, previousItems: items, fillToWidthAndHeight: false, media: media, mediaIndexCounter: &mediaIndexCounter, embedIndexCounter: &embedIndexCounter, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat) + let subLayout = layoutInstantPageBlock(webpage: webpage, rtl: rtl, block: subBlock, boundingWidth: boundingWidth - horizontalInset * 2.0 - lineInset, horizontalInset: 0.0, safeInset: 0.0, isCover: false, previousItems: items, fillToWidthAndHeight: false, media: media, mediaIndexCounter: &mediaIndexCounter, embedIndexCounter: &embedIndexCounter, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, webEmbedHeights: webEmbedHeights) let spacing = spacingBetweenBlocks(upper: previousBlock, lower: subBlock) let blockItems = subLayout.flattenedItemsWithOrigin(CGPoint(x: horizontalInset + lineInset, y: contentSize.height + spacing)) @@ -455,20 +535,9 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, block: InstantPageBlo items.append(InstantPageShapeItem(frame: CGRect(origin: CGPoint(x: horizontalInset, y: 0.0), size: CGSize(width: 3.0, height: contentSize.height)), shapeFrame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: 3.0, height: contentSize.height)), shape: .roundLine, color: theme.textCategories.paragraph.color)) - if case .empty = caption { - } else { - contentSize.height += 14.0 - - let styleStack = InstantPageTextStyleStack() - setupStyleStack(styleStack, theme: theme, category: .caption, link: false) - - let captionItem = layoutTextItemWithString(attributedStringForRichText(caption, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0) - captionItem.frame = captionItem.frame.offsetBy(dx: floor(boundingWidth - captionItem.frame.size.width) / 2.0, dy: contentSize.height) - captionItem.alignment = .center - - contentSize.height += captionItem.frame.size.height - items.append(captionItem) - } + let (captionItems, captionSize) = layoutCaption(caption, contentSize) + items.append(contentsOf: captionItems) + contentSize.height += captionSize.height return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) case let .slideshow(items: subItems, caption: caption): @@ -479,7 +548,7 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, block: InstantPageBlo for subBlock in subItems { switch subBlock { - case let .image(id, caption): + case let .image(id, caption, url, webpageId): if let image = media[id] as? TelegramMediaImage, let imageSize = largestImageRepresentation(image.representations)?.dimensions { let mediaIndex = mediaIndexCounter mediaIndexCounter += 1 @@ -487,7 +556,7 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, block: InstantPageBlo let filledSize = imageSize.fitted(CGSize(width: boundingWidth, height: 1200.0)) contentSize.height = max(contentSize.height, filledSize.height) - itemMedias.append(InstantPageMedia(index: mediaIndex, media: image, caption: caption.plainText)) + itemMedias.append(InstantPageMedia(index: mediaIndex, media: image, caption: caption.text)) } break default: @@ -497,20 +566,9 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, block: InstantPageBlo items.append(InstantPageSlideshowItem(frame: CGRect(origin: CGPoint(), size: CGSize(width: boundingWidth, height: contentSize.height)), webPage: webpage, medias: itemMedias)) - if case .empty = caption { - } else { - contentSize.height += 14.0 - - let styleStack = InstantPageTextStyleStack() - setupStyleStack(styleStack, theme: theme, category: .caption, link: false) - - let captionItem = layoutTextItemWithString(attributedStringForRichText(caption, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0) - captionItem.frame = captionItem.frame.offsetBy(dx: floor(boundingWidth - captionItem.frame.size.width) / 2.0, dy: contentSize.height) - captionItem.alignment = .center - - contentSize.height += captionItem.frame.size.height - items.append(captionItem) - } + let (captionItems, captionSize) = layoutCaption(caption, contentSize) + items.append(contentsOf: captionItems) + contentSize.height += captionSize.height return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) case let .webEmbed(url, html, dimensions, caption, stretchToWidth, allowScrolling, coverId): @@ -519,46 +577,49 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, block: InstantPageBlo embedBoundingWidth = boundingWidth } let size: CGSize - if dimensions.width.isLessThanOrEqualTo(0.0) { - size = CGSize(width: embedBoundingWidth, height: dimensions.height) + if let dimensions = dimensions { + if dimensions.width.isLessThanOrEqualTo(0.0) { + size = CGSize(width: embedBoundingWidth, height: dimensions.height) + } else { + size = dimensions.aspectFitted(CGSize(width: embedBoundingWidth, height: embedBoundingWidth)) + } } else { - size = dimensions.aspectFitted(CGSize(width: embedBoundingWidth, height: embedBoundingWidth)) + var hash: Int? + if let url = url { + hash = url.hashValue + } else if let html = html { + hash = html.hashValue + } + + if let hash = hash, let height = webEmbedHeights[hash] { + size = CGSize(width: embedBoundingWidth, height: CGFloat(height)) + } else { + size = CGSize(width: embedBoundingWidth, height: 44.0) + } } var items: [InstantPageItem] = [] - let item = InstantPageWebEmbedItem(frame: CGRect(origin: CGPoint(x: floor((boundingWidth - size.width) / 2.0), y: 0.0), size: size), url: url, html: html, enableScrolling: allowScrolling) items.append(item) var contentSize = item.frame.size - if case .empty = caption { - } else { - contentSize.height += 10.0 - - let styleStack = InstantPageTextStyleStack() - setupStyleStack(styleStack, theme: theme, category: .caption, link: false) - - let captionItem = layoutTextItemWithString(attributedStringForRichText(caption, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0) - captionItem.frame = captionItem.frame.offsetBy(dx: floor(boundingWidth - captionItem.frame.size.width) / 2.0, dy: contentSize.height) - captionItem.alignment = .center - - contentSize.height += captionItem.frame.size.height - items.append(captionItem) - } + let (captionItems, captionSize) = layoutCaption(caption, contentSize) + items.append(contentsOf: captionItems) + contentSize.height += captionSize.height return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) case let .channelBanner(peer): var contentSize = CGSize(width: boundingWidth, height: 0.0) var items: [InstantPageItem] = [] - var rtl = false + var previousItemHasRTL = false if let previousItem = previousItems.last as? InstantPageTextItem, previousItem.containsRTL { - rtl = true + previousItemHasRTL = true } if let peer = peer { - let item = InstantPagePeerReferenceItem(frame: CGRect(origin: CGPoint(), size: CGSize(width: boundingWidth, height: 40.0)), initialPeer: peer, rtl: rtl) + let item = InstantPagePeerReferenceItem(frame: CGRect(origin: CGPoint(), size: CGSize(width: boundingWidth, height: 40.0)), initialPeer: peer, rtl: rtl || previousItemHasRTL) items.append(item) contentSize.height += 40.0 } @@ -566,41 +627,133 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, block: InstantPageBlo case let .anchor(name): let item = InstantPageAnchorItem(frame: CGRect(origin: CGPoint(), size: CGSize(width: boundingWidth, height: 0.0)), anchor: name) return InstantPageLayout(origin: CGPoint(), contentSize: item.frame.size, items: [item]) - case let .audio(id: audioId, caption: caption): + case let .audio(audioId, caption): var contentSize = CGSize(width: boundingWidth, height: 0.0) var items: [InstantPageItem] = [] if let file = media[audioId] as? TelegramMediaFile { let mediaIndex = mediaIndexCounter mediaIndexCounter += 1 - let item = InstantPageAudioItem(frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: boundingWidth, height: 48.0)), media: InstantPageMedia(index: mediaIndex, media: file, caption: ""), webpage: webpage) + let item = InstantPageAudioItem(frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: boundingWidth, height: 48.0)), media: InstantPageMedia(index: mediaIndex, media: file, caption: nil), webpage: webpage) - contentSize.height += item.frame.size.height + contentSize.height += item.frame.height items.append(item) - if case .empty = caption { - } else { - contentSize.height += 10.0 - - let styleStack = InstantPageTextStyleStack() - setupStyleStack(styleStack, theme: theme, category: .caption, link: false) - - let captionItem = layoutTextItemWithString(attributedStringForRichText(caption, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0) - captionItem.frame = captionItem.frame.offsetBy(dx: floor(boundingWidth - captionItem.frame.size.width) / 2.0, dy: contentSize.height) - captionItem.alignment = .center - - contentSize.height += captionItem.frame.size.height - items.append(captionItem) + let (captionItems, captionSize) = layoutCaption(caption, contentSize) + items.append(contentsOf: captionItems) + contentSize.height += captionSize.height + } + + return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) + case let .table(title, rows, bordered, striped): + var contentSize = CGSize(width: boundingWidth, height: 0.0) + var items: [InstantPageItem] = [] + + var styleStack = InstantPageTextStyleStack() + setupStyleStack(styleStack, theme: theme, category: .caption, link: false) + let backgroundInset: CGFloat = 0.0 + let (textItems, textContentSize) = layoutTextItemWithString(attributedStringForRichText(title, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0 - backgroundInset * 2.0, offset: CGPoint(), media: media, webpage: webpage) + if let textItem = textItems.first as? InstantPageTextItem { + textItem.frame = textItem.frame.offsetBy(dx: floor(boundingWidth - textItem.frame.width) / 2.0, dy: 0.0) + textItem.alignment = .center + } + items.append(contentsOf: textItems) + contentSize.height += textContentSize.height + 10.0 + + styleStack = InstantPageTextStyleStack() + setupStyleStack(styleStack, theme: theme, category: .table, link: false) + let tableBoundingWidth = boundingWidth - horizontalInset * 2.0 + let tableItem = layoutTableItem(rtl: rtl, rows: rows, styleStack: styleStack, theme: theme, bordered: bordered, striped: striped, boundingWidth: tableBoundingWidth, horizontalInset: horizontalInset, media: media, webpage: webpage) + tableItem.frame = tableItem.frame.offsetBy(dx: 0.0, dy: contentSize.height) + + contentSize.height += tableItem.frame.height + items.append(tableItem) + + return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) + case let .details(title, blocks, open): + var contentSize = CGSize(width: boundingWidth, height: 0.0) + var subitems: [InstantPageItem] = [] + + var previousBlock: InstantPageBlock? + for subBlock in blocks { + let subLayout = layoutInstantPageBlock(webpage: webpage, rtl: rtl, block: subBlock, boundingWidth: boundingWidth - horizontalInset * 2.0, horizontalInset: 0.0, safeInset: 0.0, isCover: false, previousItems: subitems, fillToWidthAndHeight: false, media: media, mediaIndexCounter: &mediaIndexCounter, embedIndexCounter: &embedIndexCounter, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, webEmbedHeights: webEmbedHeights) + + let spacing = spacingBetweenBlocks(upper: previousBlock, lower: subBlock) + let blockItems = subLayout.flattenedItemsWithOrigin(CGPoint(x: horizontalInset, y: contentSize.height + spacing)) + subitems.append(contentsOf: blockItems) + contentSize.height += subLayout.contentSize.height + spacing + previousBlock = subBlock + } + + let styleStack = InstantPageTextStyleStack() + setupStyleStack(styleStack, theme: theme, category: .paragraph, link: false) + let detailsItem = layoutDetailsItem(theme: theme, title: attributedStringForRichText(title, styleStack: styleStack), boundingWidth: boundingWidth, items: subitems, contentSize: contentSize, open: open, rtl: rtl) + + return InstantPageLayout(origin: CGPoint(), contentSize: detailsItem.frame.size, items: [detailsItem]) + case let .relatedArticles(title, articles): + var contentSize = CGSize(width: boundingWidth, height: 0.0) + var items: [InstantPageItem] = [] + + let styleStack = InstantPageTextStyleStack() + setupStyleStack(styleStack, theme: theme, category: .paragraph, link: false) + styleStack.push(.bold) + let backgroundInset: CGFloat = 14.0 + let (textItems, textContentSize) = layoutTextItemWithString(attributedStringForRichText(title, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0 - backgroundInset * 2.0, offset: CGPoint(x: horizontalInset, y: backgroundInset), media: media, webpage: webpage) + let backgroundItem = InstantPageShapeItem(frame: CGRect(origin: CGPoint(), size: CGSize(width: boundingWidth, height: textContentSize.height + backgroundInset * 2.0)), shapeFrame: CGRect(origin: CGPoint(), size: CGSize(width: boundingWidth, height: textContentSize.height + backgroundInset * 2.0)), shape: .rect, color: theme.panelBackgroundColor) + items.append(backgroundItem) + items.append(contentsOf: textItems) + contentSize.height += backgroundItem.frame.height + + for (i, article) in articles.enumerated() { + var cover: TelegramMediaImage? + if let coverId = article.photoId { + cover = media[coverId] as? TelegramMediaImage + } + + let item = InstantPageArticleItem(frame: CGRect(origin: CGPoint(x: 0, y: contentSize.height), size: CGSize(width: boundingWidth, height: 93.0)), webPage: webpage, title: article.title ?? "", description: article.description ?? "", cover: cover, url: article.url, webpageId: article.webpageId) + contentSize.height += item.frame.height + items.append(item) + + let inset: CGFloat = i == articles.count - 1 ? 0.0 : 17.0 + let lineSize = CGSize(width: boundingWidth - inset, height: UIScreenPixel) + let shapeItem = InstantPageShapeItem(frame: CGRect(origin: CGPoint(x: rtl ? 0.0 : inset, y: contentSize.height - lineSize.height), size: lineSize), shapeFrame: CGRect(origin: CGPoint(), size: lineSize), shape: .rect, color: theme.controlColor) + items.append(shapeItem) + } + return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) + case let .map(latitude, longitude, zoom, dimensions, caption): + let imageSize = dimensions + var filledSize = imageSize.aspectFitted(CGSize(width: boundingWidth - safeInset * 2.0, height: 1200.0)) + + if fillToWidthAndHeight { + filledSize = CGSize(width: boundingWidth - safeInset * 2.0, height: boundingWidth - safeInset * 2.0) + } else if isCover { + filledSize = imageSize.aspectFilled(CGSize(width: boundingWidth - safeInset * 2.0, height: 1.0)) + if !filledSize.height.isZero { + filledSize = filledSize.cropped(CGSize(width: boundingWidth - safeInset * 2.0, height: floor((boundingWidth - safeInset * 2.0) * 3.0 / 5.0))) } } + let map = TelegramMediaMap(latitude: latitude, longitude: longitude, geoPlace: nil, venue: nil, liveBroadcastingTimeout: nil) + let attributes: [InstantPageImageAttribute] = [InstantPageMapAttribute(zoom: zoom, dimensions: dimensions)] + + var contentSize = CGSize(width: boundingWidth - safeInset * 2.0, height: 0.0) + var items: [InstantPageItem] = [] + let mediaItem = InstantPageImageItem(frame: CGRect(origin: CGPoint(x: floor((boundingWidth - filledSize.width) / 2.0), y: 0.0), size: filledSize), webPage: webpage, media: InstantPageMedia(index: -1, media: map, caption: caption.text), attributes: attributes, interactive: true, roundCorners: false, fit: false) + + items.append(mediaItem) + contentSize.height += filledSize.height + + let (captionItems, captionSize) = layoutCaption(caption, contentSize) + items.append(contentsOf: captionItems) + contentSize.height += captionSize.height + return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) default: return InstantPageLayout(origin: CGPoint(), contentSize: CGSize(), items: []) } } -func instantPageLayoutForWebPage(_ webPage: TelegramMediaWebpage, boundingWidth: CGFloat, safeInset: CGFloat, strings: PresentationStrings, theme: InstantPageTheme, dateTimeFormat: PresentationDateTimeFormat) -> InstantPageLayout { +func instantPageLayoutForWebPage(_ webPage: TelegramMediaWebpage, boundingWidth: CGFloat, safeInset: CGFloat, strings: PresentationStrings, theme: InstantPageTheme, dateTimeFormat: PresentationDateTimeFormat, webEmbedHeights: [Int : Int] = [:]) -> InstantPageLayout { var maybeLoadedContent: TelegramMediaWebpageLoadedContent? if case let .Loaded(content) = webPage.content { maybeLoadedContent = content @@ -610,6 +763,7 @@ func instantPageLayoutForWebPage(_ webPage: TelegramMediaWebpage, boundingWidth: return InstantPageLayout(origin: CGPoint(), contentSize: CGSize(), items: []) } + let rtl = instantPage.rtl let pageBlocks = instantPage.blocks var contentSize = CGSize(width: boundingWidth, height: 0.0) var items: [InstantPageItem] = [] @@ -624,7 +778,7 @@ func instantPageLayoutForWebPage(_ webPage: TelegramMediaWebpage, boundingWidth: var previousBlock: InstantPageBlock? for block in pageBlocks { - let blockLayout = layoutInstantPageBlock(webpage: webPage, block: block, boundingWidth: boundingWidth, horizontalInset: 17.0 + safeInset, safeInset: safeInset, isCover: false, previousItems: items, fillToWidthAndHeight: false, media: media, mediaIndexCounter: &mediaIndexCounter, embedIndexCounter: &embedIndexCounter, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat) + let blockLayout = layoutInstantPageBlock(webpage: webPage, rtl: rtl, block: block, boundingWidth: boundingWidth, horizontalInset: 17.0 + safeInset, safeInset: safeInset, isCover: false, previousItems: items, fillToWidthAndHeight: false, media: media, mediaIndexCounter: &mediaIndexCounter, embedIndexCounter: &embedIndexCounter, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, webEmbedHeights: webEmbedHeights) let spacing = spacingBetweenBlocks(upper: previousBlock, lower: block) let blockItems = blockLayout.flattenedItemsWithOrigin(CGPoint(x: 0.0, y: contentSize.height + spacing)) items.append(contentsOf: blockItems) @@ -637,34 +791,9 @@ func instantPageLayoutForWebPage(_ webPage: TelegramMediaWebpage, boundingWidth: let closingSpacing = spacingBetweenBlocks(upper: previousBlock, lower: nil) contentSize.height += closingSpacing + let feedbackItem = InstantPageFeedbackItem(frame: CGRect(x: 0.0, y: contentSize.height, width: boundingWidth, height: 40.0)) + contentSize.height += feedbackItem.frame.height + items.append(feedbackItem) + return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) } - -/*+ (TGInstantPageLayout *)makeLayoutForWebPage:(TGWebPageMediaAttachment *)webPage boundingWidth:(CGFloat)boundingWidth { - - NSInteger mediaIndexCounter = 0; - - TGInstantPageBlock *previousBlock = nil; - for (TGInstantPageBlock *block in pageBlocks) { - TGInstantPageLayout *blockLayout = [self layoutBlock:block boundingWidth:boundingWidth horizontalInset:17.0f isCover:false fillToWidthAndHeight:false images:images videos:webPage.instantPage.videos mediaIndexCounter:&mediaIndexCounter]; - CGFloat spacing = spacingBetweenBlocks(previousBlock, block); - NSArray *blockItems = [blockLayout flattenedItemsWithOrigin:CGPointMake(0.0f, contentSize.height + spacing)]; - [items addObjectsFromArray:blockItems]; - contentSize.height += blockLayout.contentSize.height + spacing; - previousBlock = block; - } - CGFloat closingSpacing = spacingBetweenBlocks(previousBlock, nil); - contentSize.height += closingSpacing; - - { - CGFloat height = CGCeil([TGInstantPageFooterButtonView heightForWidth:boundingWidth]); - - TGInstantPageFooterButtonItem *item = [[TGInstantPageFooterButtonItem alloc] initWithFrame:CGRectMake(0.0f, contentSize.height, boundingWidth, height)]; - [items addObject:item]; - contentSize.height += item.frame.size.height; - } - - return [[TGInstantPageLayout alloc] initWithOrigin:CGPointZero contentSize:contentSize items:items]; -}*/ - - diff --git a/TelegramUI/InstantPageLayoutSpacings.swift b/TelegramUI/InstantPageLayoutSpacings.swift index 6d1271feca..9c590f6b69 100644 --- a/TelegramUI/InstantPageLayoutSpacings.swift +++ b/TelegramUI/InstantPageLayoutSpacings.swift @@ -4,12 +4,14 @@ import TelegramCore func spacingBetweenBlocks(upper: InstantPageBlock?, lower: InstantPageBlock?) -> CGFloat { if let upper = upper, let lower = lower { switch (upper, lower) { - case (_, .cover), (_, .channelBanner): + case (_, .cover), (_, .channelBanner), (.details, .details), (.relatedArticles, nil): return 0.0 case (.divider, _), (_, .divider): return 25.0 case (_, .blockQuote), (.blockQuote, _), (_, .pullQuote), (.pullQuote, _): return 27.0 + case (.kicker, .title): + return 16.0 case (_, .title): return 20.0 case (.title, .authorDate), (.subtitle, .authorDate): @@ -35,14 +37,12 @@ func spacingBetweenBlocks(upper: InstantPageBlock?, lower: InstantPageBlock?) -> case (.preformatted, .list): return 19.0 case (_, .list): - return 20.0 + return 25.0 case (.paragraph, .preformatted): return 19.0 case (_, .preformatted): return 20.0 - case (_, .header): - return 32.0 - case (_, .subheader): + case (_, .header), (_, .subheader): return 32.0 default: return 20.0 @@ -55,6 +55,10 @@ func spacingBetweenBlocks(upper: InstantPageBlock?, lower: InstantPageBlock?) -> return 24.0 } } else { - return 24.0 + if let upper = upper, case .relatedArticles = upper { + return 0.0 + } else { + return 24.0 + } } } diff --git a/TelegramUI/InstantPageMedia.swift b/TelegramUI/InstantPageMedia.swift index de8e356870..1ab41a6caf 100644 --- a/TelegramUI/InstantPageMedia.swift +++ b/TelegramUI/InstantPageMedia.swift @@ -5,7 +5,7 @@ import TelegramCore struct InstantPageMedia: Equatable { let index: Int let media: Media - let caption: String? + let caption: RichText? static func ==(lhs: InstantPageMedia, rhs: InstantPageMedia) -> Bool { return lhs.index == rhs.index && lhs.media.isEqual(to: rhs.media) && lhs.caption == rhs.caption diff --git a/TelegramUI/InstantPagePeerReferenceItem.swift b/TelegramUI/InstantPagePeerReferenceItem.swift index 8157c53ce9..747697e777 100644 --- a/TelegramUI/InstantPagePeerReferenceItem.swift +++ b/TelegramUI/InstantPagePeerReferenceItem.swift @@ -17,7 +17,7 @@ final class InstantPagePeerReferenceItem: InstantPageItem { self.rtl = rtl } - func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void) -> (InstantPageNode & ASDisplayNode)? { + func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (Int, Int) -> Void) -> (InstantPageNode & ASDisplayNode)? { return InstantPagePeerReferenceNode(account: account, strings: strings, theme: theme, initialPeer: self.initialPeer, rtl: self.rtl, openPeer: openPeer) } @@ -34,7 +34,7 @@ final class InstantPagePeerReferenceItem: InstantPageItem { } func distanceThresholdGroup() -> Int? { - return 4 + return 5 } func distanceThresholdWithGroupCount(_ count: Int) -> CGFloat { diff --git a/TelegramUI/InstantPagePlayableVideoItem.swift b/TelegramUI/InstantPagePlayableVideoItem.swift index ce0f42de01..a37616eb29 100644 --- a/TelegramUI/InstantPagePlayableVideoItem.swift +++ b/TelegramUI/InstantPagePlayableVideoItem.swift @@ -23,7 +23,7 @@ final class InstantPagePlayableVideoItem: InstantPageItem { self.interactive = interactive } - func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void) -> (InstantPageNode & ASDisplayNode)? { + func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (Int, Int) -> Void) -> (InstantPageNode & ASDisplayNode)? { return InstantPagePlayableVideoNode(account: account, webPage: self.webPage, media: self.media, interactive: self.interactive, openMedia: openMedia) } diff --git a/TelegramUI/InstantPageSettingsThemeItemNode.swift b/TelegramUI/InstantPageSettingsThemeItemNode.swift index a2f32fd93c..2259352a6a 100644 --- a/TelegramUI/InstantPageSettingsThemeItemNode.swift +++ b/TelegramUI/InstantPageSettingsThemeItemNode.swift @@ -104,7 +104,7 @@ final class InstantPageSettingsThemeItemNode: InstantPageSettingsItemNode { InstantPageSettingsThemeSelectorNode(color: .white, edgeColor: (selectedIndex == 1 || selectedIndex == 2) ? UIColor.lightGray : UIColor.white, selectionColor: selectionColor), InstantPageSettingsThemeSelectorNode(color: UIColor(rgb: 0xcbb98e), edgeColor: UIColor(rgb: 0xcbb98e), selectionColor: selectionColor), InstantPageSettingsThemeSelectorNode(color: UIColor(rgb: 0x48484a), edgeColor: UIColor(rgb: 0x48484a), selectionColor: selectionColor), - InstantPageSettingsThemeSelectorNode(color: UIColor(rgb: 0x48484a), edgeColor: UIColor(rgb: 0x48484a), selectionColor: selectionColor) + InstantPageSettingsThemeSelectorNode(color: UIColor(rgb: 0x333333), edgeColor: UIColor(rgb: 0x333333), selectionColor: selectionColor) ] super.init(theme: theme, selectable: false) diff --git a/TelegramUI/InstantPageShapeItem.swift b/TelegramUI/InstantPageShapeItem.swift index 197efbb264..fc3be0ff18 100644 --- a/TelegramUI/InstantPageShapeItem.swift +++ b/TelegramUI/InstantPageShapeItem.swift @@ -56,7 +56,7 @@ final class InstantPageShapeItem: InstantPageItem { return false } - func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void) -> (InstantPageNode & ASDisplayNode)? { + func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (Int, Int) -> Void) -> (InstantPageNode & ASDisplayNode)? { return nil } diff --git a/TelegramUI/InstantPageSlideshowItem.swift b/TelegramUI/InstantPageSlideshowItem.swift index 1a8f452d59..9124d702f8 100644 --- a/TelegramUI/InstantPageSlideshowItem.swift +++ b/TelegramUI/InstantPageSlideshowItem.swift @@ -15,7 +15,7 @@ final class InstantPageSlideshowItem: InstantPageItem { self.medias = medias } - func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void) -> (InstantPageNode & ASDisplayNode)? { + func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (Int, Int) -> Void) -> (InstantPageNode & ASDisplayNode)? { return InstantPageSlideshowNode(account: account, webPage: webPage, medias: self.medias, openMedia: openMedia) } diff --git a/TelegramUI/InstantPageSlideshowItemNode.swift b/TelegramUI/InstantPageSlideshowItemNode.swift index 4e22a04c61..f1b147ebd2 100644 --- a/TelegramUI/InstantPageSlideshowItemNode.swift +++ b/TelegramUI/InstantPageSlideshowItemNode.swift @@ -172,7 +172,7 @@ private final class InstantPageSlideshowPagerNode: ASDisplayNode, UIScrollViewDe let media = self.items[index] let contentNode: ASDisplayNode if let _ = media.media as? TelegramMediaImage { - contentNode = InstantPageImageNode(account: self.account, webPage: self.webPage, media: media, interactive: true, roundCorners: false, fit: false, openMedia: self.openMedia) + contentNode = InstantPageImageNode(account: self.account, webPage: self.webPage, media: media, attributes: [], interactive: true, roundCorners: false, fit: false, openMedia: self.openMedia) } else if let file = media.media as? TelegramMediaFile { contentNode = ASDisplayNode() } else { diff --git a/TelegramUI/InstantPageTableItem.swift b/TelegramUI/InstantPageTableItem.swift new file mode 100644 index 0000000000..437cc5686d --- /dev/null +++ b/TelegramUI/InstantPageTableItem.swift @@ -0,0 +1,652 @@ +import Foundation +import TelegramCore +import Postbox +import Display + +private struct TableSide: OptionSet { + var rawValue: Int32 = 0 + + static let top = TableSide(rawValue: 1 << 0) + static let left = TableSide(rawValue: 1 << 1) + static let right = TableSide(rawValue: 1 << 2) + static let bottom = TableSide(rawValue: 1 << 3) + + var uiRectCorner: UIRectCorner { + var corners: UIRectCorner = [] + if self.contains(.top) && self.contains(.left) { + corners.insert(.topLeft) + } + if self.contains(.top) && self.contains(.right) { + corners.insert(.topRight) + } + if self.contains(.bottom) && self.contains(.left) { + corners.insert(.bottomLeft) + } + if self.contains(.bottom) && self.contains(.right) { + corners.insert(.bottomRight) + } + return corners + } +} + +private extension TableHorizontalAlignment { + var textAlignment: NSTextAlignment { + switch self { + case .left: + return .left + case .center: + return .center + case .right: + return .right + } + } +} + +private struct TableCellPosition { + let row: Int + let column: Int +} + +private struct InstantPageTableCellItem { + let position: TableCellPosition + let cell: InstantPageTableCell + let frame: CGRect + let filled: Bool + let textItem: InstantPageTextItem? + let additionalItems: [InstantPageItem] + let adjacentSides: TableSide + + func withRowHeight(_ height: CGFloat) -> InstantPageTableCellItem { + var frame = self.frame + frame = CGRect(x: frame.minX, y: frame.minY, width: frame.width, height: height) + return InstantPageTableCellItem(position: position, cell: self.cell, frame: frame, filled: self.filled, textItem: self.textItem, additionalItems: self.additionalItems, adjacentSides: self.adjacentSides) + } + + func withRTL(_ totalWidth: CGFloat) -> InstantPageTableCellItem { + var frame = self.frame + frame = CGRect(x: totalWidth - frame.minX - frame.width, y: frame.minY, width: frame.width, height: frame.height) + var adjacentSides = self.adjacentSides + if adjacentSides.contains(.left) { + adjacentSides.remove(.left) + adjacentSides.insert(.right) + } + if adjacentSides.contains(.right) { + adjacentSides.remove(.right) + adjacentSides.insert(.left) + } + return InstantPageTableCellItem(position: position, cell: self.cell, frame: frame, filled: self.filled, textItem: self.textItem, additionalItems: self.additionalItems, adjacentSides: adjacentSides) + } + + var verticalAlignment: TableVerticalAlignment { + return self.cell.verticalAlignment + } + + var colspan: Int { + return self.cell.colspan > 1 ? Int(clamping: self.cell.colspan) : 1 + } + + var rowspan: Int { + return self.cell.rowspan > 1 ? Int(clamping: self.cell.rowspan) : 1 + } +} + +private let tableCellInsets = UIEdgeInsetsMake(14.0, 12.0, 14.0, 12.0) +private let tableBorderWidth: CGFloat = 1.0 +private let tableCornerRadius: CGFloat = 5.0 + +final class InstantPageTableItem: InstantPageItem { + var frame: CGRect + let totalWidth: CGFloat + let horizontalInset: CGFloat + let medias: [InstantPageMedia] = [] + let wantsNode: Bool = true + + let theme: InstantPageTheme + + let rtl: Bool + fileprivate let cells: [InstantPageTableCellItem] + private let borderWidth: CGFloat + + fileprivate init(frame: CGRect, totalWidth: CGFloat, horizontalInset: CGFloat, borderWidth: CGFloat, theme: InstantPageTheme, cells: [InstantPageTableCellItem], rtl: Bool) { + self.frame = frame + self.totalWidth = totalWidth + self.horizontalInset = horizontalInset + self.borderWidth = borderWidth + self.theme = theme + self.cells = cells + self.rtl = rtl + } + + func drawInTile(context: CGContext) { + for cell in self.cells { + context.saveGState() + context.translateBy(x: cell.frame.minX, y: cell.frame.minY) + if cell.filled { + let bounds = CGRect(origin: CGPoint(), size: cell.frame.size) + context.setFillColor(self.theme.tableHeaderColor.cgColor) + if !cell.adjacentSides.isEmpty { + let path = UIBezierPath(roundedRect: bounds, byRoundingCorners: cell.adjacentSides.uiRectCorner, cornerRadii: CGSize(width: tableCornerRadius, height: tableCornerRadius)) + context.addPath(path.cgPath) + context.fillPath() + } else { + context.fill(bounds) + } + } + if let textItem = cell.textItem { + textItem.drawInTile(context: context) + } + context.restoreGState() + if self.borderWidth > 0.0 { + context.setStrokeColor(self.theme.tableBorderColor.cgColor) + context.setLineWidth(borderWidth) + if !cell.adjacentSides.contains(.right) { + context.move(to: CGPoint(x: cell.frame.maxX + borderWidth / 2.0, y: cell.frame.minY)) + context.addLine(to: CGPoint(x: cell.frame.maxX + borderWidth / 2.0, y: cell.frame.maxY + borderWidth)) + context.strokePath() + } + if !cell.adjacentSides.contains(.bottom) { + context.move(to: CGPoint(x: cell.frame.minX, y: cell.frame.maxY + borderWidth / 2.0)) + context.addLine(to: CGPoint(x: cell.frame.maxX, y: cell.frame.maxY + borderWidth / 2.0)) + context.strokePath() + } + } + } + + if self.borderWidth > 0.0 { + context.setStrokeColor(self.theme.tableBorderColor.cgColor) + context.setLineWidth(borderWidth) + let path = UIBezierPath(roundedRect: CGRect(x: borderWidth / 2.0, y: borderWidth / 2.0, width: self.totalWidth - borderWidth, height: self.frame.height - borderWidth), cornerRadius: tableCornerRadius) + + context.addPath(path.cgPath) + context.strokePath() + } + } + + func matchesAnchor(_ anchor: String) -> Bool { + return false + } + + func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (Int, Int) -> Void) -> (InstantPageNode & ASDisplayNode)? { + return InstantPageTableNode(item: self, account: account, strings: strings, theme: theme) + } + + func matchesNode(_ node: InstantPageNode) -> Bool { + if let node = node as? InstantPageTableNode { + return node.item === self + } + return false + } + + func distanceThresholdGroup() -> Int? { + return nil + } + + func distanceThresholdWithGroupCount(_ count: Int) -> CGFloat { + return 0.0 + } + + func linkSelectionRects(at point: CGPoint) -> [CGRect] { + for cell in self.cells { + if let item = cell.textItem, item.selectable, item.frame.insetBy(dx: -tableCellInsets.left, dy: -tableCellInsets.top).contains(point.offsetBy(dx: -cell.frame.minX - self.horizontalInset, dy: -cell.frame.minY)) { + let rects = item.linkSelectionRects(at: point.offsetBy(dx: -cell.frame.minX - self.horizontalInset - item.frame.minX, dy: -cell.frame.minY - item.frame.minY)) + return rects.map { $0.offsetBy(dx: cell.frame.minX + item.frame.minX + self.horizontalInset, dy: cell.frame.minY + item.frame.minY) } + } + } + return [] + } + + func textItemAtLocation(_ location: CGPoint) -> (InstantPageTextItem, CGPoint)? { + for cell in self.cells { + if let item = cell.textItem, item.selectable, item.frame.insetBy(dx: -tableCellInsets.left, dy: -tableCellInsets.top).contains(location.offsetBy(dx: -cell.frame.minX - self.horizontalInset, dy: -cell.frame.minY)) { + return (item, cell.frame.origin.offsetBy(dx: self.horizontalInset, dy: 0.0)) + } + } + return nil + } +} + +private final class InstantPageTableNodeParameters: NSObject { + let item: InstantPageTableItem + + init(item: InstantPageTableItem) { + self.item = item + super.init() + } +} + +final class InstantPageTableContentNode: ASDisplayNode { + private let item: InstantPageTableItem + + init(item: InstantPageTableItem, account: Account, strings: PresentationStrings, theme: InstantPageTheme) { + self.item = item + super.init() + + self.isOpaque = false + self.isUserInteractionEnabled = false + + for cell in self.item.cells { + for item in cell.additionalItems { + if item.wantsNode { + if let node = item.node(account: account, strings: strings, theme: theme, openMedia: { _ in }, openPeer: { _ in }, openUrl: { _ in}, updateWebEmbedHeight: { _, _ in }) { + node.frame = item.frame.offsetBy(dx: cell.frame.minX, dy: cell.frame.minY) + self.addSubnode(node) + } + } + } + } + } + + override func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? { + return InstantPageTableNodeParameters(item: self.item) + } + + @objc override public class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) { + let context = UIGraphicsGetCurrentContext()! + + if let parameters = parameters as? InstantPageTableNodeParameters { + parameters.item.drawInTile(context: context) + } + } +} + +final class InstantPageTableNode: ASScrollNode, InstantPageNode { + let item: InstantPageTableItem + let contentNode: InstantPageTableContentNode + + var contentOffset: CGPoint { + return self.view.contentOffset + } + + init(item: InstantPageTableItem, account: Account, strings: PresentationStrings, theme: InstantPageTheme) { + self.item = item + self.contentNode = InstantPageTableContentNode(item: item, account: account, strings: strings, theme: theme) + super.init() + + self.isOpaque = false + self.contentNode.frame = CGRect(x: item.horizontalInset, y: 0.0, width: item.totalWidth, height: item.frame.height) + self.view.contentSize = CGSize(width: item.totalWidth + item.horizontalInset * 2.0, height: item.frame.height) + if item.rtl { + self.view.contentOffset = CGPoint(x: self.view.contentSize.width - item.frame.width, y: 0.0) + } + self.view.alwaysBounceVertical = false + self.view.showsHorizontalScrollIndicator = false + self.view.showsVerticalScrollIndicator = false + if #available(iOSApplicationExtension 11.0, *) { + self.view.contentInsetAdjustmentBehavior = .never + } + self.addSubnode(self.contentNode) + + self.view.interactiveTransitionGestureRecognizerTest = { [weak self] point -> Bool in + if let strongSelf = self { + if strongSelf.view.contentOffset.x < 1.0 { + return false + } else { + return point.x - strongSelf.view.contentOffset.x > 30.0 + } + } else { + return false + } + } + } + + func updateIsVisible(_ isVisible: Bool) { + + } + + func transitionNode(media: InstantPageMedia) -> (ASDisplayNode, () -> UIView?)? { + return nil + } + + func updateHiddenMedia(media: InstantPageMedia?) { + + } + + func update(strings: PresentationStrings, theme: InstantPageTheme) { + } +} + +private struct TableRow { + var minColumnWidths: [Int : CGFloat] + var maxColumnWidths: [Int : CGFloat] +} + +private func offsetForHorizontalAlignment(_ alignment: TableHorizontalAlignment, width: CGFloat, boundingWidth: CGFloat, insets: UIEdgeInsets) -> CGFloat { + switch alignment { + case .left: + return insets.left + case .center: + return (boundingWidth - width) / 2.0 + case .right: + return boundingWidth - width - insets.right + } +} + +private func offestForVerticalAlignment(_ verticalAlignment: TableVerticalAlignment, height: CGFloat, boundingHeight: CGFloat, insets: UIEdgeInsets) -> CGFloat { + switch verticalAlignment { + case .top: + return insets.top + case .middle: + return (boundingHeight - height) / 2.0 + case .bottom: + return boundingHeight - height - insets.bottom + } +} + +func layoutTableItem(rtl: Bool, rows: [InstantPageTableRow], styleStack: InstantPageTextStyleStack, theme: InstantPageTheme, bordered: Bool, striped: Bool, boundingWidth: CGFloat, horizontalInset: CGFloat, media: [MediaId: Media], webpage: TelegramMediaWebpage) -> InstantPageTableItem { + if rows.count == 0 { + return InstantPageTableItem(frame: CGRect(), totalWidth: 0.0, horizontalInset: 0.0, borderWidth: 0.0, theme: theme, cells: [], rtl: rtl) + } + + let borderWidth = bordered ? tableBorderWidth : 0.0 + let totalCellPadding = tableCellInsets.left + tableCellInsets.right + let cellWidthLimit = boundingWidth - totalCellPadding + var tableRows: [TableRow] = [] + var columnCount: Int = 0 + + var columnSpans: [Range : (CGFloat, CGFloat)] = [:] + + for row in rows { + var minColumnWidths: [Int : CGFloat] = [:] + var maxColumnWidths: [Int : CGFloat] = [:] + var i: Int = 0 + for cell in row.cells { + var minCellWidth: CGFloat = 1.0 + var maxCellWidth: CGFloat = 1.0 + if let text = cell.text { + if let shortestTextItem = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: cellWidthLimit, offset: CGPoint(), media: media, webpage: webpage, minimizeWidth: true).0.first as? InstantPageTextItem { + minCellWidth = shortestTextItem.effectiveWidth() + totalCellPadding + } + + if let longestTextItem = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: cellWidthLimit, offset: CGPoint(), media: media, webpage: webpage).0.first as? InstantPageTextItem { + maxCellWidth = longestTextItem.effectiveWidth() + totalCellPadding + } + } + if cell.colspan > 1 { + minColumnWidths[i] = 1.0 + maxColumnWidths[i] = 1.0 + + let spanRange = i ..< i + Int(cell.colspan) + if let (minSpanWidth, maxSpanWidth) = columnSpans[spanRange] { + columnSpans[spanRange] = (max(minSpanWidth, minCellWidth), max(maxSpanWidth, maxCellWidth)) + } else { + columnSpans[spanRange] = (minCellWidth, maxCellWidth) + } + } else { + minColumnWidths[i] = minCellWidth + maxColumnWidths[i] = maxCellWidth + } + i += cell.colspan > 1 ? Int(clamping: cell.colspan) : 1 + } + tableRows.append(TableRow(minColumnWidths: minColumnWidths, maxColumnWidths: maxColumnWidths)) + columnCount = max(columnCount, row.cells.count) + } + + let maxContentWidth = boundingWidth - borderWidth * CGFloat(columnCount - 1) + var availableWidth = maxContentWidth + var minColumnWidths: [Int : CGFloat] = [:] + var maxColumnWidths: [Int : CGFloat] = [:] + var maxTotalWidth: CGFloat = 0.0 + for i in 0 ..< columnCount { + var minWidth: CGFloat = 1.0 + var maxWidth: CGFloat = 1.0 + for row in tableRows { + if let columnWidth = row.minColumnWidths[i] { + minWidth = max(minWidth, columnWidth) + } + if let columnWidth = row.maxColumnWidths[i] { + maxWidth = max(maxWidth, columnWidth) + } + } + minColumnWidths[i] = minWidth + maxColumnWidths[i] = maxWidth + availableWidth -= minWidth + maxTotalWidth += maxWidth + } + + for (range, span) in columnSpans { + let (minSpanWidth, maxSpanWidth) = span + + var minWidth: CGFloat = 0.0 + var maxWidth: CGFloat = 0.0 + for i in range { + if let columnWidth = minColumnWidths[i] { + minWidth += columnWidth + } + if let columnWidth = maxColumnWidths[i] { + maxWidth += columnWidth + } + } + + if minWidth < minSpanWidth { + let delta = minSpanWidth - minWidth + for i in range { + if let columnWidth = minColumnWidths[i] { + let growth = round(delta / CGFloat(range.count)) + minColumnWidths[i] = columnWidth + growth + availableWidth -= growth + } + } + } + + if maxWidth < maxSpanWidth { + let delta = maxSpanWidth - maxWidth + for i in range { + if let columnWidth = maxColumnWidths[i] { + let growth = round(delta / CGFloat(range.count)) + maxColumnWidths[i] = columnWidth + growth + maxTotalWidth += growth + } + } + } + } + + var totalWidth = maxTotalWidth + var finalColumnWidths: [Int : CGFloat] = [:] + let widthToDistribute: CGFloat + if availableWidth > 0 { + widthToDistribute = availableWidth + finalColumnWidths = minColumnWidths + } else { + widthToDistribute = maxContentWidth - maxTotalWidth + finalColumnWidths = maxColumnWidths + } + + if widthToDistribute > 0.0 { + for i in 0 ..< finalColumnWidths.count { + var width = finalColumnWidths[i]! + let maxWidth = maxColumnWidths[i]! + let growth = round(widthToDistribute * maxWidth / maxTotalWidth) + width += growth + availableWidth -= growth + finalColumnWidths[i] = width + } + totalWidth = boundingWidth + } + + var finalizedCells: [InstantPageTableCellItem] = [] + var origin: CGPoint = CGPoint() + var totalHeight: CGFloat = 0.0 + var rowHeights: [Int : CGFloat] = [:] + + var deferredCells: [Int : [(Int, InstantPageTableCellItem)]] = [:] + + for i in 0 ..< rows.count { + let row = rows[i] + var maxCellHeight: CGFloat = 1.0 + origin.x = 0.0 + + var k: Int = 0 + var rowCells: [InstantPageTableCellItem] = [] + + if let cells = deferredCells[i] { + for colAndCell in cells { + let cell = colAndCell.1 + if cell.position.column == k { + for j in 0 ..< cell.colspan { + if let width = finalColumnWidths[k + j] { + origin.x += width + } + } + origin.x += borderWidth * CGFloat(cell.colspan) + k += cell.colspan + } else { + break + } + } + } + + for cell in row.cells { + var cellWidth: CGFloat = 0.0 + let colspan: Int = cell.colspan > 1 ? Int(clamping: cell.colspan) : 1 + let rowspan: Int = cell.rowspan > 1 ? Int(clamping: cell.rowspan) : 1 + for j in 0 ..< colspan { + if let width = finalColumnWidths[k + j] { + cellWidth += width + } + } + cellWidth += borderWidth * CGFloat(colspan - 1) + + var item: InstantPageTextItem? + var additionalItems: [InstantPageItem] = [] + var cellHeight: CGFloat? + if let text = cell.text { + let (items, _) = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: cellWidth - totalCellPadding, offset: CGPoint(), media: media, webpage: webpage) + if let textItem = items.first as? InstantPageTextItem { + textItem.alignment = cell.alignment.textAlignment + textItem.frame = textItem.frame.offsetBy(dx: tableCellInsets.left, dy: 0.0) + cellHeight = ceil(textItem.frame.height) + tableCellInsets.top + tableCellInsets.bottom + + item = textItem + } + for var item in items where !(item is InstantPageTextItem) { + let offset = offsetForHorizontalAlignment(cell.alignment, width: item.frame.width, boundingWidth: cellWidth, insets: tableCellInsets) + item.frame = item.frame.offsetBy(dx: offset, dy: 0.0) + + let height = ceil(item.frame.height) + tableCellInsets.top + tableCellInsets.bottom - 10.0 + if let currentCellHeight = cellHeight { + cellHeight = max(currentCellHeight, height) + } else { + cellHeight = height + } + + additionalItems.append(item) + } + } + var filled = cell.header + if !filled && striped { + filled = i % 2 == 0 + } + var adjacentSides: TableSide = [] + if i == 0 { + adjacentSides.insert(.top) + } + if i == rows.count - 1 { + adjacentSides.insert(.bottom) + } + if k == 0 { + adjacentSides.insert(.left) + } + if k + colspan == columnCount { + adjacentSides.insert(.right) + } + let rowCell = InstantPageTableCellItem(position: TableCellPosition(row: i, column: k), cell: cell, frame: CGRect(x: origin.x, y: origin.y, width: cellWidth, height: 10.0), filled: filled, textItem: item, additionalItems: additionalItems, adjacentSides: adjacentSides) + if rowspan == 1 { + rowCells.append(rowCell) + if let cellHeight = cellHeight { + maxCellHeight = max(maxCellHeight, cellHeight) + } + } else { + for j in i ..< i + rowspan { + if deferredCells[j] == nil { + deferredCells[j] = [(k, rowCell)] + } else { + deferredCells[j]!.append((k, rowCell)) + } + } + } + + k += colspan + origin.x += cellWidth + borderWidth + } + + let finalizeCell: (InstantPageTableCellItem, inout [InstantPageTableCellItem], CGFloat) -> Void = { cell, cells, height in + let updatedCell = cell.withRowHeight(height) + if let textItem = updatedCell.textItem { + let offset = offestForVerticalAlignment(cell.verticalAlignment, height: textItem.frame.height, boundingHeight: height, insets: tableCellInsets) + updatedCell.textItem!.frame = textItem.frame.offsetBy(dx: 0.0, dy: offset) + + for var item in updatedCell.additionalItems { + item.frame = item.frame.offsetBy(dx: 0.0, dy: offset) + } + } else { + for var item in updatedCell.additionalItems { + let offset = offestForVerticalAlignment(cell.verticalAlignment, height: item.frame.height, boundingHeight: height, insets: tableCellInsets) + item.frame = item.frame.offsetBy(dx: 0.0, dy: offset) + } + } + cells.append(updatedCell) + } + + for cell in rowCells { + finalizeCell(cell, &finalizedCells, maxCellHeight) + } + + rowHeights[i] = maxCellHeight + + var completedSpans = [Int : Set]() + if let cells = deferredCells[i] { + for colAndCell in cells { + let cell = colAndCell.1 + let utmostRow = cell.position.row + cell.rowspan - 1 + if rowHeights[utmostRow] == nil { + continue + } + + var cellHeight: CGFloat = 0.0 + for k in cell.position.row ..< utmostRow + 1 { + if let height = rowHeights[k] { + cellHeight += height + } + + if completedSpans[k] == nil { + var set = Set() + set.insert(colAndCell.0) + completedSpans[k] = set + } else { + completedSpans[k]!.insert(colAndCell.0) + } + } + cellHeight += borderWidth * CGFloat(cell.rowspan - 1) + + finalizeCell(cell, &finalizedCells, cellHeight) + } + } + + if !completedSpans.isEmpty { + deferredCells = deferredCells.reduce([Int : [(Int, InstantPageTableCellItem)]]()) { (current, rowAndValue) in + var result = current + let cells = rowAndValue.value.filter({ column, cell in + if let completedSpansInRow = completedSpans[rowAndValue.key] { + return !completedSpansInRow.contains(column) + } else { + return true + } + }) + if !cells.isEmpty { + result[rowAndValue.key] = cells + } + return result + } + } + + totalHeight += maxCellHeight + origin.y += maxCellHeight + borderWidth + } + totalHeight += borderWidth * CGFloat(rows.count - 1) + + if rtl { + finalizedCells = finalizedCells.map { $0.withRTL(totalWidth) } + } + + return InstantPageTableItem(frame: CGRect(x: 0.0, y: 0.0, width: boundingWidth + horizontalInset * 2.0, height: totalHeight), totalWidth: totalWidth, horizontalInset: horizontalInset, borderWidth: borderWidth, theme: theme, cells: finalizedCells, rtl: rtl) +} diff --git a/TelegramUI/InstantPageTextItem.swift b/TelegramUI/InstantPageTextItem.swift index 65f9c86bb2..5a52b03d89 100644 --- a/TelegramUI/InstantPageTextItem.swift +++ b/TelegramUI/InstantPageTextItem.swift @@ -1,5 +1,6 @@ import Foundation import TelegramCore +import Display import Postbox import AsyncDisplayKit @@ -13,22 +14,37 @@ final class InstantPageUrlItem { } } +struct InstantPageTextMarkedItem { + let frame: CGRect + let color: UIColor +} + struct InstantPageTextStrikethroughItem { let frame: CGRect } +struct InstantPageTextImageItem { + let frame: CGRect + let range: NSRange + let id: MediaId +} + final class InstantPageTextLine { let line: CTLine let range: NSRange let frame: CGRect let strikethroughItems: [InstantPageTextStrikethroughItem] + let markedItems: [InstantPageTextMarkedItem] + let imageItems: [InstantPageTextImageItem] let isRTL: Bool - init(line: CTLine, range: NSRange, frame: CGRect, strikethroughItems: [InstantPageTextStrikethroughItem], isRTL: Bool) { + init(line: CTLine, range: NSRange, frame: CGRect, strikethroughItems: [InstantPageTextStrikethroughItem], markedItems: [InstantPageTextMarkedItem], imageItems: [InstantPageTextImageItem], isRTL: Bool) { self.line = line self.range = range self.frame = frame self.strikethroughItems = strikethroughItems + self.markedItems = markedItems + self.imageItems = imageItems self.isRTL = isRTL } } @@ -41,11 +57,20 @@ final class InstantPageTextItem: InstantPageItem { var alignment: NSTextAlignment = .natural let medias: [InstantPageMedia] = [] let wantsNode: Bool = false + var selectable: Bool = true var containsRTL: Bool { return !self.rtlLineIndices.isEmpty } + var imageItems: [InstantPageTextImageItem] { + return self.lines.reduce([InstantPageTextImageItem]()) { (items, line) in + var items = items + items.append(contentsOf: line.imageItems) + return items + } + } + init(frame: CGRect, attributedString: NSAttributedString, lines: [InstantPageTextLine]) { self.attributedString = attributedString self.frame = frame @@ -65,7 +90,7 @@ final class InstantPageTextItem: InstantPageItem { context.saveGState() context.textMatrix = CGAffineTransform(scaleX: 1.0, y: -1.0) context.translateBy(x: self.frame.minX, y: self.frame.minY) - + let clipRect = context.boundingBoxOfClipPath let upperOriginBound = clipRect.minY - 10.0 @@ -90,6 +115,21 @@ final class InstantPageTextItem: InstantPageItem { } context.textPosition = CGPoint(x: lineOrigin.x, y: lineOrigin.y + lineFrame.size.height) + + if !line.markedItems.isEmpty { + context.saveGState() + for item in line.markedItems { + context.setFillColor(item.color.cgColor) + + let height = floor(item.frame.size.height * 2.2) + let rect = CGRect(x: item.frame.minX - 2.0, y: floor(item.frame.minY + (item.frame.height - height) / 2.0), width: item.frame.width + 4.0, height: height) + let path = UIBezierPath.init(roundedRect: rect, cornerRadius: 3.0) + context.addPath(path.cgPath) + context.fillPath() + } + context.restoreGState() + } + CTLineDraw(line.line, context) if !line.strikethroughItems.isEmpty { @@ -116,35 +156,6 @@ final class InstantPageTextItem: InstantPageItem { } else if self.alignment == .natural && self.rtlLineIndices.contains(i) { lineFrame.origin.x = boundsWidth - lineFrame.size.width } - if lineFrame.contains(transformedPoint) { - var index = CTLineGetStringIndexForPosition(line.line, CGPoint(x: transformedPoint.x - lineFrame.minX, y: transformedPoint.y - lineFrame.minY)) - if index == attributedString.length { - index -= 1 - } else if index != 0 { - var glyphStart: CGFloat = 0.0 - CTLineGetOffsetForStringIndex(line.line, index, &glyphStart) - if transformedPoint.x < glyphStart { - index -= 1 - } - } - if index >= 0 && index < attributedString.length { - return (index, attributedString.attributes(at: index, effectiveRange: nil)) - } - break - } - } - for i in 0 ..< self.lines.count { - let line = self.lines[i] - - var lineFrame = CGRect(origin: CGPoint(x: line.frame.origin.x, y: line.frame.origin.y), size: line.frame.size) - if self.alignment == .center { - lineFrame.origin.x = floor((boundsWidth - lineFrame.size.width) / 2.0) - } else if self.alignment == .right { - lineFrame.origin.x = boundsWidth - lineFrame.size.width - } else if self.alignment == .natural && self.rtlLineIndices.contains(i) { - lineFrame.origin.x = boundsWidth - lineFrame.size.width - } - if lineFrame.insetBy(dx: -5.0, dy: -5.0).contains(transformedPoint) { var index = CTLineGetStringIndexForPosition(line.line, CGPoint(x: transformedPoint.x - lineFrame.minX, y: transformedPoint.y - lineFrame.minY)) if index == attributedString.length { @@ -191,7 +202,6 @@ final class InstantPageTextItem: InstantPageItem { } else if self.alignment == .natural && self.rtlLineIndices.contains(i) { lineFrame.origin.x = boundsWidth - lineFrame.size.width } - rects.append(CGRect(origin: CGPoint(x: lineFrame.minX + leftOffset, y: lineFrame.minY), size: CGSize(width: rightOffset - leftOffset, height: lineFrame.size.height))) } } @@ -199,7 +209,6 @@ final class InstantPageTextItem: InstantPageItem { return rects } } - return nil } @@ -211,7 +220,6 @@ final class InstantPageTextItem: InstantPageItem { } } } - return [] } @@ -227,10 +235,25 @@ final class InstantPageTextItem: InstantPageItem { func lineRects() -> [CGRect] { let boundsWidth = self.frame.width var rects: [CGRect] = [] + var topLeft = CGPoint(x: CGFloat.greatestFiniteMagnitude, y: 0.0) + var bottomRight = CGPoint() + + var lastLineFrame: CGRect? for i in 0 ..< self.lines.count { let line = self.lines[i] var lineFrame = CGRect(origin: CGPoint(x: line.frame.origin.x, y: line.frame.origin.y), size: line.frame.size) + for imageItem in line.imageItems { + if imageItem.frame.minY < lineFrame.minY { + let delta = lineFrame.minY - imageItem.frame.minY - 2.0 + lineFrame = CGRect(x: lineFrame.minX, y: lineFrame.minY - delta, width: lineFrame.width, height: lineFrame.height + delta) + } + if imageItem.frame.maxY > lineFrame.maxY { + let delta = imageItem.frame.maxY - lineFrame.maxY - 2.0 + lineFrame = CGRect(x: lineFrame.minX, y: lineFrame.minY, width: lineFrame.width, height: lineFrame.height + delta) + } + } + lineFrame = lineFrame.insetBy(dx: 0.0, dy: -4.0) if self.alignment == .center { lineFrame.origin.x = floor((boundsWidth - lineFrame.size.width) / 2.0) } else if self.alignment == .right { @@ -239,11 +262,42 @@ final class InstantPageTextItem: InstantPageItem { lineFrame.origin.x = boundsWidth - lineFrame.size.width } - rects.append(lineFrame) + if lineFrame.minX < topLeft.x { + topLeft = CGPoint(x: lineFrame.minX, y: topLeft.y) + } + if lineFrame.maxX > bottomRight.x { + bottomRight = CGPoint(x: lineFrame.maxX, y: bottomRight.y) + } + + if self.lines.count > 1 && i == self.lines.count - 1 { + lastLineFrame = lineFrame + } else { + if lineFrame.minY < topLeft.y { + topLeft = CGPoint(x: topLeft.x, y: lineFrame.minY) + } + if lineFrame.maxY > bottomRight.y { + bottomRight = CGPoint(x: bottomRight.x, y: lineFrame.maxY) + } + } } + rects.append(CGRect(x: topLeft.x, y: topLeft.y, width: bottomRight.x - topLeft.x, height: bottomRight.y - topLeft.y)) + if self.lines.count > 1, var lastLineFrame = lastLineFrame { + let delta = lastLineFrame.minY - bottomRight.y + lastLineFrame = CGRect(x: lastLineFrame.minX, y: bottomRight.y, width: lastLineFrame.width, height: lastLineFrame.height + delta) + rects.append(lastLineFrame) + } + return rects } + func effectiveWidth() -> CGFloat { + var width: CGFloat = 0.0 + for line in self.lines { + width = max(width, line.frame.width) + } + return ceil(width) + } + func plainText() -> String { if let first = self.lines.first, let last = self.lines.last { return self.attributedString.attributedSubstring(from: NSMakeRange(first.range.location, last.range.location + last.range.length - first.range.location)).string @@ -255,7 +309,7 @@ final class InstantPageTextItem: InstantPageItem { return false } - func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void) -> (InstantPageNode & ASDisplayNode)? { + func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (Int, Int) -> Void) -> (InstantPageNode & ASDisplayNode)? { return nil } @@ -326,17 +380,71 @@ func attributedStringForRichText(_ text: RichText, styleStack: InstantPageTextSt string.append(substring) } return string + case let .subscript(text): + styleStack.push(.subscript) + let result = attributedStringForRichText(text, styleStack: styleStack, url: url) + styleStack.pop() + return result + case let .superscript(text): + styleStack.push(.superscript) + let result = attributedStringForRichText(text, styleStack: styleStack, url: url) + styleStack.pop() + return result + case let .marked(text): + styleStack.push(.marker) + let result = attributedStringForRichText(text, styleStack: styleStack, url: url) + styleStack.pop() + return result + case let .phone(text, phone): + styleStack.push(.bold) + styleStack.push(.underline) + let result = attributedStringForRichText(text, styleStack: styleStack, url: InstantPageUrlItem(url: "tel:\(phone)", webpageId: nil)) + styleStack.pop() + styleStack.pop() + return result + case let .image(id, dimensions): + struct RunStruct { + let ascent: CGFloat + let descent: CGFloat + let width: CGFloat + } + let extentBuffer = UnsafeMutablePointer.allocate(capacity: 1) + extentBuffer.initialize(to: RunStruct(ascent: dimensions.height, descent: 0.0, width: dimensions.width)) + var callbacks = CTRunDelegateCallbacks(version: kCTRunDelegateVersion1, dealloc: { (pointer) in + }, getAscent: { (pointer) -> CGFloat in + let d = pointer.assumingMemoryBound(to: RunStruct.self) + return d.pointee.ascent + }, getDescent: { (pointer) -> CGFloat in + let d = pointer.assumingMemoryBound(to: RunStruct.self) + return d.pointee.descent + }, getWidth: { (pointer) -> CGFloat in + let d = pointer.assumingMemoryBound(to: RunStruct.self) + return d.pointee.width + }) + let delegate = CTRunDelegateCreate(&callbacks, extentBuffer) + let attrDictionaryDelegate = [(kCTRunDelegateAttributeName as NSAttributedStringKey): (delegate as Any), NSAttributedStringKey(rawValue: InstantPageMediaIdAttribute): id.id] + return NSAttributedString(string: " ", attributes: attrDictionaryDelegate) } } -func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFloat) -> InstantPageTextItem { +func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFloat, offset: CGPoint, media: [MediaId: Media] = [:], webpage: TelegramMediaWebpage? = nil, minimizeWidth: Bool = false) -> ([InstantPageItem], CGSize) { if string.length == 0 { - return InstantPageTextItem(frame: CGRect(), attributedString: string, lines: []) + return ([], CGSize()) } var lines: [InstantPageTextLine] = [] - guard let font = string.attribute(NSAttributedStringKey.font, at: 0, effectiveRange: nil) as? UIFont else { - return InstantPageTextItem(frame: CGRect(), attributedString: string, lines: []) + var font = string.attribute(NSAttributedStringKey.font, at: 0, effectiveRange: nil) as? UIFont + if font == nil { + let range = NSMakeRange(0, string.length) + string.enumerateAttributes(in: range, options: []) { attributes, range, _ in + if font == nil, let furtherFont = attributes[NSAttributedStringKey.font] as? UIFont { + font = furtherFont + } + } + } + let image = string.attribute(NSAttributedStringKey.init(rawValue: InstantPageMediaIdAttribute), at: 0, effectiveRange: nil) + guard font != nil || image != nil else { + return ([], CGSize()) } var lineSpacingFactor: CGFloat = 1.12 @@ -345,8 +453,8 @@ func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFlo } let typesetter = CTTypesetterCreateWithAttributedString(string) - let fontAscent = font.ascender - let fontDescent = font.descender + let fontAscent = font?.ascender ?? 0.0 + let fontDescent = font?.descender ?? 0.0 let fontLineHeight = floor(fontAscent + fontDescent) let fontLineSpacing = floor(fontLineHeight * lineSpacingFactor) @@ -354,45 +462,105 @@ func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFlo var lastIndex: CFIndex = 0 var currentLineOrigin = CGPoint() + var maxImageHeight: CGFloat = 0.0 + var extraDescent: CGFloat = 0.0 + let text = string.string + var indexOffset: CFIndex? while true { let currentMaxWidth = boundingWidth - currentLineOrigin.x - let currentLineInset: CGFloat = 0.0 - let lineCharacterCount = CTTypesetterSuggestLineBreak(typesetter, lastIndex, Double(currentMaxWidth)) - + let lineCharacterCount: CFIndex + var hadIndexOffset = false + if minimizeWidth { + var count = 0 + for ch in text.suffix(text.count - lastIndex) { + count += 1 + if ch == " " || ch == "\n" || ch == "\t" { + break + } + } + lineCharacterCount = count + } else { + let suggestedLineBreak = CTTypesetterSuggestLineBreak(typesetter, lastIndex, Double(currentMaxWidth)) + if let offset = indexOffset { + lineCharacterCount = suggestedLineBreak + offset + indexOffset = nil + hadIndexOffset = true + } else { + lineCharacterCount = suggestedLineBreak + } + } if lineCharacterCount > 0 { let line = CTTypesetterCreateLineWithOffset(typesetter, CFRangeMake(lastIndex, lineCharacterCount), 100.0) - - let trailingWhitespace = CGFloat(CTLineGetTrailingWhitespaceWidth(line)) - let lineWidth = CGFloat(CTLineGetTypographicBounds(line, nil, nil, nil) + Double(currentLineInset)) - - var strikethroughItems: [InstantPageTextStrikethroughItem] = [] - + let lineWidth = CGFloat(CTLineGetTypographicBounds(line, nil, nil, nil)) let lineRange = NSMakeRange(lastIndex, lineCharacterCount) - string.enumerateAttribute(NSAttributedStringKey.strikethroughStyle, in: lineRange, options: [], using: { item, range, _ in - if let item = item { + var strikethroughItems: [InstantPageTextStrikethroughItem] = [] + var markedItems: [InstantPageTextMarkedItem] = [] + + string.enumerateAttributes(in: lineRange, options: []) { attributes, range, _ in + if let _ = attributes[NSAttributedStringKey.strikethroughStyle] { let lowerX = floor(CTLineGetOffsetForStringIndex(line, range.location, nil)) let upperX = ceil(CTLineGetOffsetForStringIndex(line, range.location + range.length, nil)) - strikethroughItems.append(InstantPageTextStrikethroughItem(frame: CGRect(x: currentLineOrigin.x + lowerX, y: currentLineOrigin.y, width: upperX - lowerX, height: fontLineHeight))) - } - }) - - var isRTL = false - let glyphRuns = CTLineGetGlyphRuns(line) as NSArray - if glyphRuns.count != 0 { - let run = glyphRuns[0] as! CTRun - if CTRunGetStatus(run).contains(CTRunStatus.rightToLeft) { - isRTL = true + } else if let item = attributes[NSAttributedStringKey.init(rawValue: InstantPageMarkerColorAttribute)] as? UIColor { + let lowerX = floor(CTLineGetOffsetForStringIndex(line, range.location, nil)) + let upperX = ceil(CTLineGetOffsetForStringIndex(line, range.location + range.length, nil)) + markedItems.append(InstantPageTextMarkedItem(frame: CGRect(x: currentLineOrigin.x + lowerX, y: currentLineOrigin.y, width: upperX - lowerX, height: fontLineHeight), color: item)) } } - let textLine = InstantPageTextLine(line: line, range: lineRange, frame: CGRect(x: currentLineOrigin.x, y: currentLineOrigin.y, width: lineWidth, height: fontLineHeight), strikethroughItems: strikethroughItems, isRTL: isRTL) + extraDescent = 0.0 + var imageItems: [InstantPageTextImageItem] = [] + var isRTL = false + if let glyphRuns = CTLineGetGlyphRuns(line) as? [CTRun], !glyphRuns.isEmpty { + if let run = glyphRuns.first, CTRunGetStatus(run).contains(CTRunStatus.rightToLeft) { + isRTL = true + } + + var appliedLineOffset: CGFloat = 0.0 + for run in glyphRuns { + let cfRunRange = CTRunGetStringRange(run) + let runRange = NSMakeRange(cfRunRange.location == kCFNotFound ? NSNotFound : cfRunRange.location, cfRunRange.length) + string.enumerateAttributes(in: runRange, options: []) { attributes, range, _ in + if let id = attributes[NSAttributedStringKey.init(rawValue: InstantPageMediaIdAttribute)] as? Int64 { + var imageFrame = CGRect() + var ascent: CGFloat = 0 + imageFrame.size.width = CGFloat(CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, nil, nil)) + imageFrame.size.height = ascent + + let xOffset = CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, nil) + let yOffset = fontLineHeight.isZero ? 0.0 : floorToScreenPixels((fontLineHeight - imageFrame.size.height) / 2.0) + imageFrame.origin = imageFrame.origin.offsetBy(dx: currentLineOrigin.x + xOffset, dy: currentLineOrigin.y + yOffset) + + let minSpacing = fontLineSpacing - 3.0 + let delta = currentLineOrigin.y - minSpacing - imageFrame.minY - appliedLineOffset + if !fontAscent.isZero && delta > 0.0 { + currentLineOrigin.y += delta + appliedLineOffset += delta + imageFrame.origin = imageFrame.origin.offsetBy(dx: 0.0, dy: delta) + } + if !fontLineHeight.isZero { + extraDescent = max(extraDescent, imageFrame.maxY - (currentLineOrigin.y + fontLineHeight + minSpacing)) + } + maxImageHeight = max(maxImageHeight, imageFrame.height) + imageItems.append(InstantPageTextImageItem(frame: imageFrame, range: range, id: MediaId(namespace: Namespaces.Media.CloudFile, id: id))) + } + } + } + } + + if !minimizeWidth && !hadIndexOffset && lineWidth > currentMaxWidth, let imageItem = imageItems.last { + indexOffset = -(lastIndex + lineCharacterCount - imageItem.range.lowerBound) + continue + } + + let height = !fontLineHeight.isZero ? fontLineHeight : maxImageHeight + let textLine = InstantPageTextLine(line: line, range: lineRange, frame: CGRect(x: currentLineOrigin.x, y: currentLineOrigin.y, width: lineWidth, height: height), strikethroughItems: strikethroughItems, markedItems: markedItems, imageItems: imageItems, isRTL: isRTL) lines.append(textLine) currentLineOrigin.x = 0.0; - currentLineOrigin.y += fontLineHeight + fontLineSpacing + currentLineOrigin.y += fontLineHeight + fontLineSpacing + extraDescent lastIndex += lineCharacterCount } else { @@ -402,8 +570,23 @@ func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFlo var height: CGFloat = 0.0 if !lines.isEmpty { - height = lines.last!.frame.maxY + height = lines.last!.frame.maxY + extraDescent } - return InstantPageTextItem(frame: CGRect(x: 0.0, y: 0.0, width: boundingWidth, height: height), attributedString: string, lines: lines) + let textItem = InstantPageTextItem(frame: CGRect(x: 0.0, y: 0.0, width: boundingWidth, height: height), attributedString: string, lines: lines) + textItem.frame = textItem.frame.offsetBy(dx: offset.x, dy: offset.y) + var items: [InstantPageItem] = [] + if textItem.imageItems.isEmpty || string.length > 1 { + items.append(textItem) + } + + if let webpage = webpage { + for imageItem in textItem.imageItems { + if let image = media[imageItem.id] as? TelegramMediaFile { + items.append(InstantPageImageItem(frame: imageItem.frame.offsetBy(dx: offset.x, dy: offset.y), webPage: webpage, media: InstantPageMedia(index: -1, media: image, caption: nil), interactive: false, roundCorners: false, fit: false)) + } + } + } + + return (items, textItem.frame.size) } diff --git a/TelegramUI/InstantPageTextStyleStack.swift b/TelegramUI/InstantPageTextStyleStack.swift index 3d10a6bb51..9e603a920b 100644 --- a/TelegramUI/InstantPageTextStyleStack.swift +++ b/TelegramUI/InstantPageTextStyleStack.swift @@ -12,9 +12,15 @@ enum InstantPageTextStyle { case underline case strikethrough case textColor(UIColor) + case `subscript` + case superscript + case markerColor(UIColor) + case marker } let InstantPageLineSpacingFactorAttribute = "LineSpacingFactorAttribute" +let InstantPageMarkerColorAttribute = "MarkerColorAttribute" +let InstantPageMediaIdAttribute = "MediaIdAttribute" final class InstantPageTextStyleStack { private var items: [InstantPageTextStyle] = [] @@ -39,6 +45,9 @@ final class InstantPageTextStyleStack { var underline: Bool? var color: UIColor? var lineSpacingFactor: CGFloat? + var baselineOffset: CGFloat? + var markerColor: UIColor? + var marker: Bool? for item in self.items.reversed() { switch item { @@ -78,6 +87,23 @@ final class InstantPageTextStyleStack { if lineSpacingFactor == nil { lineSpacingFactor = value } + case .subscript: + if baselineOffset == nil { + baselineOffset = 0.35 + underline = false + } + case .superscript: + if baselineOffset == nil { + baselineOffset = -0.35 + } + case let .markerColor(color): + if markerColor == nil { + markerColor = color + } + case .marker: + if marker == nil { + marker = true + } } } @@ -90,6 +116,11 @@ final class InstantPageTextStyleStack { parsedFontSize = 16.0 } + if let baselineOffset = baselineOffset { + attributes[NSAttributedStringKey.baselineOffset] = round(parsedFontSize * baselineOffset); + parsedFontSize = round(parsedFontSize * 0.85) + } + if (bold != nil && bold!) && (italic != nil && italic!) { if fontSerif != nil && fontSerif! { attributes[NSAttributedStringKey.font] = UIFont(name: "Georgia-BoldItalic", size: parsedFontSize) @@ -142,6 +173,10 @@ final class InstantPageTextStyleStack { attributes[NSAttributedStringKey(rawValue: InstantPageLineSpacingFactorAttribute)] = lineSpacingFactor as NSNumber } + if marker != nil && marker!, let markerColor = markerColor { + attributes[NSAttributedStringKey(rawValue: InstantPageMarkerColorAttribute)] = markerColor + } + return attributes } } diff --git a/TelegramUI/InstantPageTheme.swift b/TelegramUI/InstantPageTheme.swift index 79cc8a40cf..2f81e0e93e 100644 --- a/TelegramUI/InstantPageTheme.swift +++ b/TelegramUI/InstantPageTheme.swift @@ -33,20 +33,26 @@ struct InstantPageTextAttributes { } enum InstantPageTextCategoryType { + case kicker case header case subheader case paragraph case caption + case table } struct InstantPageTextCategories { + let kicker: InstantPageTextAttributes let header: InstantPageTextAttributes let subheader: InstantPageTextAttributes let paragraph: InstantPageTextAttributes let caption: InstantPageTextAttributes + let table: InstantPageTextAttributes func attributes(type: InstantPageTextCategoryType, link: Bool) -> InstantPageTextAttributes { switch type { + case .kicker: + return self.kicker.withUnderline(link) case .header: return self.header.withUnderline(link) case .subheader: @@ -55,11 +61,13 @@ struct InstantPageTextCategories { return self.paragraph.withUnderline(link) case .caption: return self.caption.withUnderline(link) + case .table: + return self.table.withUnderline(link) } } func withUpdatedFontStyles(sizeMultiplier: CGFloat, forceSerif: Bool) -> InstantPageTextCategories { - return InstantPageTextCategories(header: self.header.withUpdatedFontStyles(sizeMultiplier: sizeMultiplier, forceSerif: forceSerif), subheader: self.subheader.withUpdatedFontStyles(sizeMultiplier: sizeMultiplier, forceSerif: forceSerif), paragraph: self.paragraph.withUpdatedFontStyles(sizeMultiplier: sizeMultiplier, forceSerif: forceSerif), caption: self.caption.withUpdatedFontStyles(sizeMultiplier: sizeMultiplier, forceSerif: forceSerif)) + return InstantPageTextCategories(kicker: self.kicker.withUpdatedFontStyles(sizeMultiplier: sizeMultiplier, forceSerif: forceSerif), header: self.header.withUpdatedFontStyles(sizeMultiplier: sizeMultiplier, forceSerif: forceSerif), subheader: self.subheader.withUpdatedFontStyles(sizeMultiplier: sizeMultiplier, forceSerif: forceSerif), paragraph: self.paragraph.withUpdatedFontStyles(sizeMultiplier: sizeMultiplier, forceSerif: forceSerif), caption: self.caption.withUpdatedFontStyles(sizeMultiplier: sizeMultiplier, forceSerif: forceSerif), table: self.table.withUpdatedFontStyles(sizeMultiplier: sizeMultiplier, forceSerif: forceSerif)) } } @@ -67,11 +75,13 @@ final class InstantPageTheme { let pageBackgroundColor: UIColor let textCategories: InstantPageTextCategories + let serif: Bool let codeBlockBackgroundColor: UIColor let textHighlightColor: UIColor let linkHighlightColor: UIColor + let markerColor: UIColor let panelBackgroundColor: UIColor let panelHighlightedBackgroundColor: UIColor @@ -79,94 +89,132 @@ final class InstantPageTheme { let panelSecondaryColor: UIColor let panelAccentColor: UIColor - init(pageBackgroundColor: UIColor, textCategories: InstantPageTextCategories, codeBlockBackgroundColor: UIColor, textHighlightColor: UIColor, linkHighlightColor: UIColor, panelBackgroundColor: UIColor, panelHighlightedBackgroundColor: UIColor, panelPrimaryColor: UIColor, panelSecondaryColor: UIColor, panelAccentColor: UIColor) { + let tableBorderColor: UIColor + let tableHeaderColor: UIColor + + let controlColor: UIColor + + init(pageBackgroundColor: UIColor, textCategories: InstantPageTextCategories, serif: Bool, codeBlockBackgroundColor: UIColor, textHighlightColor: UIColor, linkHighlightColor: UIColor, markerColor: UIColor, panelBackgroundColor: UIColor, panelHighlightedBackgroundColor: UIColor, panelPrimaryColor: UIColor, panelSecondaryColor: UIColor, panelAccentColor: UIColor, tableBorderColor: UIColor, tableHeaderColor: UIColor, controlColor: UIColor) { self.pageBackgroundColor = pageBackgroundColor self.textCategories = textCategories + self.serif = serif self.codeBlockBackgroundColor = codeBlockBackgroundColor self.textHighlightColor = textHighlightColor self.linkHighlightColor = linkHighlightColor + self.markerColor = markerColor self.panelBackgroundColor = panelBackgroundColor self.panelHighlightedBackgroundColor = panelHighlightedBackgroundColor self.panelPrimaryColor = panelPrimaryColor self.panelSecondaryColor = panelSecondaryColor self.panelAccentColor = panelAccentColor + self.tableBorderColor = tableBorderColor + self.tableHeaderColor = tableHeaderColor + self.controlColor = controlColor } func withUpdatedFontStyles(sizeMultiplier: CGFloat, forceSerif: Bool) -> InstantPageTheme { - return InstantPageTheme(pageBackgroundColor: pageBackgroundColor, textCategories: self.textCategories.withUpdatedFontStyles(sizeMultiplier: sizeMultiplier, forceSerif: forceSerif), codeBlockBackgroundColor: codeBlockBackgroundColor, textHighlightColor: textHighlightColor, linkHighlightColor: linkHighlightColor, panelBackgroundColor: panelBackgroundColor, panelHighlightedBackgroundColor: panelHighlightedBackgroundColor, panelPrimaryColor: panelPrimaryColor, panelSecondaryColor: panelSecondaryColor, panelAccentColor: panelAccentColor) + return InstantPageTheme(pageBackgroundColor: pageBackgroundColor, textCategories: self.textCategories.withUpdatedFontStyles(sizeMultiplier: sizeMultiplier, forceSerif: forceSerif), serif: forceSerif, codeBlockBackgroundColor: codeBlockBackgroundColor, textHighlightColor: textHighlightColor, linkHighlightColor: linkHighlightColor, markerColor: markerColor, panelBackgroundColor: panelBackgroundColor, panelHighlightedBackgroundColor: panelHighlightedBackgroundColor, panelPrimaryColor: panelPrimaryColor, panelSecondaryColor: panelSecondaryColor, panelAccentColor: panelAccentColor, tableBorderColor: tableBorderColor, tableHeaderColor: tableHeaderColor, controlColor: controlColor) } } private let lightTheme = InstantPageTheme( pageBackgroundColor: .white, textCategories: InstantPageTextCategories( + kicker: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 0.685), color: .black), header: InstantPageTextAttributes(font: InstantPageFont(style: .serif, size: 24.0, lineSpacingFactor: 0.685), color: .black), subheader: InstantPageTextAttributes(font: InstantPageFont(style: .serif, size: 19.0, lineSpacingFactor: 0.685), color: .black), paragraph: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 17.0, lineSpacingFactor: 1.0), color: .black), - caption: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 17.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0x79828b)) + caption: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0x79828b)), + table: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 1.0), color: .black) ), + serif: false, codeBlockBackgroundColor: UIColor(rgb: 0xf5f8fc), textHighlightColor: UIColor(rgb: 0, alpha: 0.12), linkHighlightColor: UIColor(rgb: 0, alpha: 0.12), + markerColor: UIColor(rgb: 0xfef3bc), panelBackgroundColor: UIColor(rgb: 0xf3f4f5), panelHighlightedBackgroundColor: UIColor(rgb: 0xe7e7e7), panelPrimaryColor: .black, panelSecondaryColor: UIColor(rgb: 0x79828b), - panelAccentColor: UIColor(rgb: 0x007ee5) + panelAccentColor: UIColor(rgb: 0x007ee5), + tableBorderColor: UIColor(rgb: 0xe2e2e2), + tableHeaderColor: UIColor(rgb: 0xf4f4f4), + controlColor: UIColor(rgb: 0xc7c7cd) ) private let sepiaTheme = InstantPageTheme( pageBackgroundColor: UIColor(rgb: 0xf8f1e2), textCategories: InstantPageTextCategories( + kicker: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 0.685), color: UIColor(rgb: 0x4f321d)), header: InstantPageTextAttributes(font: InstantPageFont(style: .serif, size: 24.0, lineSpacingFactor: 0.685), color: UIColor(rgb: 0x4f321d)), subheader: InstantPageTextAttributes(font: InstantPageFont(style: .serif, size: 19.0, lineSpacingFactor: 0.685), color: UIColor(rgb: 0x4f321d)), paragraph: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 17.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0x4f321d)), - caption: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 17.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0x927e6b)) + caption: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0x927e6b)), + table: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0x4f321d)) ), + serif: false, codeBlockBackgroundColor: UIColor(rgb: 0xefe7d6), textHighlightColor: UIColor(rgb: 0, alpha: 0.1), linkHighlightColor: UIColor(rgb: 0, alpha: 0.1), + markerColor: UIColor(rgb: 0xe5ddcd), panelBackgroundColor: UIColor(rgb: 0xefe7d6), panelHighlightedBackgroundColor: UIColor(rgb: 0xe3dccb), panelPrimaryColor: .black, panelSecondaryColor: UIColor(rgb: 0x927e6b), - panelAccentColor: UIColor(rgb: 0xd19601) + panelAccentColor: UIColor(rgb: 0xd19601), + tableBorderColor: UIColor(rgb: 0xddd1b8), + tableHeaderColor: UIColor(rgb: 0xf0e7d4), + controlColor: UIColor(rgb: 0xddd1b8) ) private let grayTheme = InstantPageTheme( pageBackgroundColor: UIColor(rgb: 0x5a5a5c), textCategories: InstantPageTextCategories( + kicker: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 0.685), color: UIColor(rgb: 0xcecece)), header: InstantPageTextAttributes(font: InstantPageFont(style: .serif, size: 24.0, lineSpacingFactor: 0.685), color: UIColor(rgb: 0xcecece)), subheader: InstantPageTextAttributes(font: InstantPageFont(style: .serif, size: 19.0, lineSpacingFactor: 0.685), color: UIColor(rgb: 0xcecece)), paragraph: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 17.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0xcecece)), - caption: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 17.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0xa0a0a0)) + caption: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0xa0a0a0)), + table: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0xcecece)) ), + serif: false, codeBlockBackgroundColor: UIColor(rgb: 0x555556), textHighlightColor: UIColor(rgb: 0, alpha: 0.16), linkHighlightColor: UIColor(rgb: 0, alpha: 0.16), + markerColor: UIColor(rgb: 0x4b4b4b), panelBackgroundColor: UIColor(rgb: 0x555556), panelHighlightedBackgroundColor: UIColor(rgb: 0x505051), panelPrimaryColor: UIColor(rgb: 0xcecece), panelSecondaryColor: UIColor(rgb: 0xa0a0a0), - panelAccentColor: UIColor(rgb: 0x54b9f8) + panelAccentColor: UIColor(rgb: 0x54b9f8), + tableBorderColor: UIColor(rgb: 0x484848), + tableHeaderColor: UIColor(rgb: 0x555556), + controlColor: UIColor(rgb: 0x484848) ) private let darkTheme = InstantPageTheme( pageBackgroundColor: UIColor(rgb: 0x000000), textCategories: InstantPageTextCategories( + kicker: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 0.685), color: UIColor(rgb: 0xb0b0b0)), header: InstantPageTextAttributes(font: InstantPageFont(style: .serif, size: 24.0, lineSpacingFactor: 0.685), color: UIColor(rgb: 0xb0b0b0)), subheader: InstantPageTextAttributes(font: InstantPageFont(style: .serif, size: 19.0, lineSpacingFactor: 0.685), color: UIColor(rgb: 0xb0b0b0)), paragraph: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 17.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0xb0b0b0)), - caption: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 17.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0x6a6a6a)) + caption: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0x6a6a6a)), + table: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0xb0b0b0)) ), + serif: false, codeBlockBackgroundColor: UIColor(rgb: 0x131313), textHighlightColor: UIColor(rgb: 0xffffff, alpha: 0.1), linkHighlightColor: UIColor(rgb: 0xffffff, alpha: 0.1), + markerColor: UIColor(rgb: 0x313131), panelBackgroundColor: UIColor(rgb: 0x131313), panelHighlightedBackgroundColor: UIColor(rgb: 0x1f1f1f), panelPrimaryColor: UIColor(rgb: 0xb0b0b0), panelSecondaryColor: UIColor(rgb: 0x6a6a6a), - panelAccentColor: UIColor(rgb: 0x50b6f3) + panelAccentColor: UIColor(rgb: 0x50b6f3), + tableBorderColor: UIColor(rgb: 0x303030), + tableHeaderColor: UIColor(rgb: 0x131313), + controlColor: UIColor(rgb: 0x303030) ) private func fontSizeMultiplierForVariant(_ variant: InstantPagePresentationFontSize) -> CGFloat { diff --git a/TelegramUI/InstantPageTile.swift b/TelegramUI/InstantPageTile.swift index 1333b66f46..1cb4acd845 100644 --- a/TelegramUI/InstantPageTile.swift +++ b/TelegramUI/InstantPageTile.swift @@ -14,6 +14,8 @@ final class InstantPageTile { for item in self.items { item.drawInTile(context: context) } +// context.setFillColor(UIColor.red.cgColor) +// context.fill(CGRect(x: 0.0, y: self.frame.maxY - 1.0, width: self.frame.width, height: 2.0)) context.translateBy(x: self.frame.minX, y: self.frame.minY) } } diff --git a/TelegramUI/InstantPageWebEmbedItem.swift b/TelegramUI/InstantPageWebEmbedItem.swift index 14546e807b..45d9d34ff8 100644 --- a/TelegramUI/InstantPageWebEmbedItem.swift +++ b/TelegramUI/InstantPageWebEmbedItem.swift @@ -19,8 +19,8 @@ final class InstantPageWebEmbedItem: InstantPageItem { self.enableScrolling = enableScrolling } - func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void) -> (InstantPageNode & ASDisplayNode)? { - return instantPageWebEmbedNode(frame: self.frame, url: self.url, html: self.html, enableScrolling: self.enableScrolling) + func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (Int, Int) -> Void) -> (InstantPageNode & ASDisplayNode)? { + return InstantPageWebEmbedNode(frame: self.frame, url: self.url, html: self.html, enableScrolling: self.enableScrolling, updateWebEmbedHeight: updateWebEmbedHeight) } func matchesAnchor(_ anchor: String) -> Bool { @@ -28,7 +28,7 @@ final class InstantPageWebEmbedItem: InstantPageItem { } func matchesNode(_ node: InstantPageNode) -> Bool { - if let node = node as? instantPageWebEmbedNode { + if let node = node as? InstantPageWebEmbedNode { return self.url == node.url && self.html == node.html } else { return false @@ -36,7 +36,7 @@ final class InstantPageWebEmbedItem: InstantPageItem { } func distanceThresholdGroup() -> Int? { - return 3 + return 6 } func distanceThresholdWithGroupCount(_ count: Int) -> CGFloat { diff --git a/TelegramUI/InstantPageWebEmbedNode.swift b/TelegramUI/InstantPageWebEmbedNode.swift index 288f8acaf3..ef1876fdbb 100644 --- a/TelegramUI/InstantPageWebEmbedNode.swift +++ b/TelegramUI/InstantPageWebEmbedNode.swift @@ -3,42 +3,118 @@ import TelegramCore import WebKit import AsyncDisplayKit -final class instantPageWebEmbedNode: ASDisplayNode, InstantPageNode { +private class WeakInstantPageWebEmbedNodeMessageHandler: NSObject, WKScriptMessageHandler { + private let f: (WKScriptMessage) -> () + + init(_ f: @escaping (WKScriptMessage) -> ()) { + self.f = f + + super.init() + } + + func userContentController(_ controller: WKUserContentController, didReceive scriptMessage: WKScriptMessage) { + self.f(scriptMessage) + } +} + +final class InstantPageWebEmbedNode: ASDisplayNode, InstantPageNode { let url: String? let html: String? + let updateWebEmbedHeight: (Int, Int) -> Void - private let webView: WKWebView + private var webView: WKWebView? - init(frame: CGRect, url: String?, html: String?, enableScrolling: Bool) { + init(frame: CGRect, url: String?, html: String?, enableScrolling: Bool, updateWebEmbedHeight: @escaping (Int, Int) -> Void) { self.url = url self.html = html - - self.webView = WKWebView(frame: CGRect(origin: CGPoint(), size: frame.size)) + self.updateWebEmbedHeight = updateWebEmbedHeight super.init() + let js = "var TelegramWebviewProxyProto = function() {}; " + + "TelegramWebviewProxyProto.prototype.postEvent = function(eventName, eventData) { " + + "window.webkit.messageHandlers.performAction.postMessage({'eventName': eventName, 'eventData': eventData}); " + + "}; " + + "var TelegramWebviewProxy = new TelegramWebviewProxyProto();" + + let configuration = WKWebViewConfiguration() + let userController = WKUserContentController() + + let userScript = WKUserScript(source: js, injectionTime: .atDocumentStart, forMainFrameOnly: false) + userController.addUserScript(userScript) + + userController.add(WeakInstantPageWebEmbedNodeMessageHandler { [weak self] message in + if let strongSelf = self { + strongSelf.handleScriptMessage(message) + } + }, name: "performAction") + + configuration.userContentController = userController + + let webView = WKWebView(frame: CGRect(origin: CGPoint(), size: frame.size), configuration: configuration) + if #available(iOSApplicationExtension 9.0, *) { + webView.allowsLinkPreview = false + } + if #available(iOSApplicationExtension 11.0, *) { + webView.scrollView.contentInsetAdjustmentBehavior = .never + } + webView.scrollView.isScrollEnabled = enableScrolling + if let html = html { - self.webView.loadHTMLString(html, baseURL: nil) + webView.loadHTMLString(html, baseURL: nil) } else if let url = url, let parsedUrl = URL(string: url) { var request = URLRequest(url: parsedUrl) if let scheme = parsedUrl.scheme, let host = parsedUrl.host { let referrer = "\(scheme)://\(host)" request.setValue(referrer, forHTTPHeaderField: "Referer") } - self.webView.load(request) + webView.load(request) + } + self.webView = webView + } + + private func handleScriptMessage(_ message: WKScriptMessage) { + guard let body = message.body as? [String: Any] else { + return + } + + guard let eventName = body["eventName"] as? String, let eventString = body["eventData"] as? String else { + return + } + + guard let eventData = eventString.data(using: .utf8) else { + return + } + + guard let dict = (try? JSONSerialization.jsonObject(with: eventData, options: [])) as? [String: Any] else { + return + } + + if eventName == "resize_frame", let height = dict["height"] as? Int { + var hash: Int? + if let url = self.url { + hash = url.hashValue + } else if let html = self.html { + hash = html.hashValue + } + if let hash = hash { + self.updateWebEmbedHeight(hash, height) + } } } override func didLoad() { super.didLoad() - self.view.addSubview(self.webView) + if let webView = self.webView { + self.view.addSubview(webView) + } } override func layout() { super.layout() - self.webView.frame = self.bounds + self.webView?.frame = self.bounds } func transitionNode(media: InstantPageMedia) -> (ASDisplayNode, () -> UIView?)? { diff --git a/TelegramUI/ItemListEditableItem.swift b/TelegramUI/ItemListEditableItem.swift index 2f3f02881c..d31e27e34e 100644 --- a/TelegramUI/ItemListEditableItem.swift +++ b/TelegramUI/ItemListEditableItem.swift @@ -283,8 +283,8 @@ class ItemListRevealOptionsItemNode: ListViewItemNode, UIGestureRecognizerDelega if !self.revealOptions.left.isEmpty { let revealNode = ItemListRevealOptionsNode(optionSelected: { [weak self] option in self?.revealOptionSelected(option, animated: false) - }, tapticAction: { [weak self] in - self?.hapticTap() + }, tapticAction: { [weak self] in + self?.hapticImpact() }) revealNode.setOptions(self.revealOptions.left) self.leftRevealNode = revealNode @@ -304,8 +304,8 @@ class ItemListRevealOptionsItemNode: ListViewItemNode, UIGestureRecognizerDelega if !self.revealOptions.right.isEmpty { let revealNode = ItemListRevealOptionsNode(optionSelected: { [weak self] option in self?.revealOptionSelected(option, animated: false) - }, tapticAction: { [weak self] in - self?.hapticTap() + }, tapticAction: { [weak self] in + self?.hapticImpact() }) revealNode.setOptions(self.revealOptions.right) self.rightRevealNode = revealNode @@ -435,10 +435,10 @@ class ItemListRevealOptionsItemNode: ListViewItemNode, UIGestureRecognizerDelega } } - private func hapticTap() { + private func hapticImpact() { if self.hapticFeedback == nil { self.hapticFeedback = HapticFeedback() } - self.hapticFeedback?.tap() + self.hapticFeedback?.impact(.medium) } } diff --git a/TelegramUI/LegacyController.swift b/TelegramUI/LegacyController.swift index 5a6388a76b..b9bb54d0a7 100644 --- a/TelegramUI/LegacyController.swift +++ b/TelegramUI/LegacyController.swift @@ -316,6 +316,7 @@ public class LegacyController: ViewController { var sizeClassSignal: SSignal { return self.sizeClass.signal()! } + private var enableContainerLayoutUpdates = false public init(presentation: LegacyControllerPresentation, theme: PresentationTheme? = nil, strings: PresentationStrings? = nil, initialLayout: ContainerViewLayout? = nil) { self.sizeClass.set(SSignal.single(UIUserInterfaceSizeClass.compact.rawValue as NSNumber)) @@ -393,7 +394,7 @@ public class LegacyController: ViewController { override public func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - + self.enableContainerLayoutUpdates = true if self.ignoreAppearanceMethodInvocations() { return } @@ -448,7 +449,9 @@ public class LegacyController: ViewController { } legacyTelegramController._updateInset(for: orientation, force: false, notify: true) - legacyTelegramController.layoutController(for: layout.size, duration: duration) + if self.enableContainerLayoutUpdates { + legacyTelegramController.layoutController(for: layout.size, duration: duration) + } } let updatedSizeClass: UIUserInterfaceSizeClass if case .regular = layout.metrics.widthClass { diff --git a/TelegramUI/LegacyLocationController.swift b/TelegramUI/LegacyLocationController.swift index 5e212e6d21..4ee25cac35 100644 --- a/TelegramUI/LegacyLocationController.swift +++ b/TelegramUI/LegacyLocationController.swift @@ -114,9 +114,7 @@ private func telegramMap(for location: TGLocationMediaAttachment) -> TelegramMed return TelegramMediaMap(latitude: location.latitude, longitude: location.longitude, geoPlace: nil, venue: mapVenue, liveBroadcastingTimeout: nil) } -func legacyLocationController(message: Message, mapMedia: TelegramMediaMap, account: Account, modal: Bool, openPeer: @escaping (Peer) -> Void, sendLiveLocation: @escaping (CLLocationCoordinate2D, Int32) -> Void, stopLiveLocation: @escaping () -> Void, openUrl: @escaping (String) -> Void) -> ViewController { - let legacyAuthor: AnyObject? = message.author.flatMap(makeLegacyPeer) - +func legacyLocationController(message: Message?, mapMedia: TelegramMediaMap, account: Account, modal: Bool, openPeer: @escaping (Peer) -> Void, sendLiveLocation: @escaping (CLLocationCoordinate2D, Int32) -> Void, stopLiveLocation: @escaping () -> Void, openUrl: @escaping (String) -> Void) -> ViewController { let legacyLocation = TGLocationMediaAttachment() legacyLocation.latitude = mapMedia.latitude legacyLocation.longitude = mapMedia.longitude @@ -127,69 +125,85 @@ func legacyLocationController(message: Message, mapMedia: TelegramMediaMap, acco let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } let legacyController = LegacyController(presentation: modal ? .modal(animateIn: true) : .navigation, theme: presentationData.theme, strings: presentationData.strings) - - let legacyMessage = makeLegacyMessage(message) - let controller: TGLocationViewController - let updatedLocations = SSignal(generator: { subscriber in - let disposable = topPeerActiveLiveLocationMessages(viewTracker: account.viewTracker, accountPeerId: account.peerId, peerId: message.id.peerId).start(next: { (_, messages) in - var result: [Any] = [] - let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) - loop: for message in messages { - var liveBroadcastingTimeout: Int32 = 0 - - mediaLoop: for media in message.media { - if let map = media as? TelegramMediaMap, let timeout = map.liveBroadcastingTimeout { - liveBroadcastingTimeout = timeout - break mediaLoop - } - } - - let legacyMessage = makeLegacyMessage(message) - guard let legacyAuthor = message.author.flatMap(makeLegacyPeer) else { - continue loop - } - let remainingTime = max(0, message.timestamp + liveBroadcastingTimeout - currentTime) - if legacyMessage.locationAttachment?.period != 0 { - let hasOwnSession = message.localTags.contains(.OutgoingLiveLocation) - var isOwn = false - if !message.flags.contains(.Incoming) { - isOwn = true - } else if let peer = message.peers[message.id.peerId] as? TelegramChannel { - isOwn = peer.hasAdminRights(.canPostMessages) + if let message = message { + let legacyMessage = makeLegacyMessage(message) + let legacyAuthor: AnyObject? = message.author.flatMap(makeLegacyPeer) + + let updatedLocations = SSignal(generator: { subscriber in + let disposable = topPeerActiveLiveLocationMessages(viewTracker: account.viewTracker, accountPeerId: account.peerId, peerId: message.id.peerId).start(next: { (_, messages) in + var result: [Any] = [] + let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) + loop: for message in messages { + var liveBroadcastingTimeout: Int32 = 0 + + mediaLoop: for media in message.media { + if let map = media as? TelegramMediaMap, let timeout = map.liveBroadcastingTimeout { + liveBroadcastingTimeout = timeout + break mediaLoop + } } - let liveLocation = TGLiveLocation(message: legacyMessage, peer: legacyAuthor, hasOwnSession: hasOwnSession, isOwnLocation: isOwn, isExpired: remainingTime == 0)! - result.append(liveLocation) + let legacyMessage = makeLegacyMessage(message) + guard let legacyAuthor = message.author.flatMap(makeLegacyPeer) else { + continue loop + } + let remainingTime = max(0, message.timestamp + liveBroadcastingTimeout - currentTime) + if legacyMessage.locationAttachment?.period != 0 { + let hasOwnSession = message.localTags.contains(.OutgoingLiveLocation) + var isOwn = false + if !message.flags.contains(.Incoming) { + isOwn = true + } else if let peer = message.peers[message.id.peerId] as? TelegramChannel { + isOwn = peer.hasAdminRights(.canPostMessages) + } + + let liveLocation = TGLiveLocation(message: legacyMessage, peer: legacyAuthor, hasOwnSession: hasOwnSession, isOwnLocation: isOwn, isExpired: remainingTime == 0)! + result.append(liveLocation) + } } + subscriber?.putNext(result) + }) + + return SBlockDisposable(block: { + disposable.dispose() + }) + })! + + if let liveBroadcastingTimeout = mapMedia.liveBroadcastingTimeout { + let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) + let remainingTime = max(0, message.timestamp + liveBroadcastingTimeout - currentTime) + + let messageLiveLocation = TGLiveLocation(message: legacyMessage, peer: legacyAuthor, hasOwnSession: false, isOwnLocation: false, isExpired: remainingTime == 0)! + + controller = TGLocationViewController(context: legacyController.context, liveLocation: messageLiveLocation) + + if remainingTime == 0 { + let freezeLocations: [Any] = [messageLiveLocation] + controller.setLiveLocationsSignal(.single(freezeLocations)) + } else { + controller.setLiveLocationsSignal(updatedLocations) } - subscriber?.putNext(result) - }) - - return SBlockDisposable(block: { - disposable.dispose() - }) - })! - - if let liveBroadcastingTimeout = mapMedia.liveBroadcastingTimeout { - let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) - let remainingTime = max(0, message.timestamp + liveBroadcastingTimeout - currentTime) - - let messageLiveLocation = TGLiveLocation(message: legacyMessage, peer: legacyAuthor, hasOwnSession: false, isOwnLocation: false, isExpired: remainingTime == 0)! - - controller = TGLocationViewController(context: legacyController.context, liveLocation: messageLiveLocation) - - if remainingTime == 0 { - let freezeLocations: [Any] = [messageLiveLocation] - controller.setLiveLocationsSignal(.single(freezeLocations)) } else { + controller = TGLocationViewController(context: legacyController.context, message: legacyMessage, peer: legacyAuthor)! + controller.receivingPeer = message.peers[message.id.peerId].flatMap(makeLegacyPeer) controller.setLiveLocationsSignal(updatedLocations) } + + let namespacesWithEnabledLiveLocation: Set = Set([ + Namespaces.Peer.CloudChannel, + Namespaces.Peer.CloudGroup, + Namespaces.Peer.CloudUser + ]) + if namespacesWithEnabledLiveLocation.contains(message.id.peerId.namespace) { + controller.allowLiveLocationSharing = true + } } else { - controller = TGLocationViewController(context: legacyController.context, message: legacyMessage, peer: legacyAuthor)! - controller.receivingPeer = message.peers[message.id.peerId].flatMap(makeLegacyPeer) - controller.setLiveLocationsSignal(updatedLocations) + let attachment = TGLocationMediaAttachment() + attachment.latitude = mapMedia.latitude + attachment.longitude = mapMedia.longitude + controller = TGLocationViewController(context: legacyController.context, locationAttachment: attachment, peer: nil) } controller.remainingTimeForMessage = { message in @@ -204,15 +218,6 @@ func legacyLocationController(message: Message, mapMedia: TelegramMediaMap, acco legacyController?.dismiss() } - let namespacesWithEnabledLiveLocation: Set = Set([ - Namespaces.Peer.CloudChannel, - Namespaces.Peer.CloudGroup, - Namespaces.Peer.CloudUser - ]) - if namespacesWithEnabledLiveLocation.contains(message.id.peerId.namespace) { - controller.allowLiveLocationSharing = true - } - let theme = (account.telegramApplicationContext.currentPresentationData.with { $0 }).theme let listTheme = theme.list diff --git a/TelegramUI/LegqacyICloudFileController.swift b/TelegramUI/LegqacyICloudFileController.swift index 48d99e09c1..45ccfb711f 100644 --- a/TelegramUI/LegqacyICloudFileController.swift +++ b/TelegramUI/LegqacyICloudFileController.swift @@ -58,6 +58,7 @@ func legacyICloudFileController(theme: PresentationTheme, completion: @escaping controller.popoverPresentationController?.sourceView = window controller.popoverPresentationController?.sourceRect = CGRect(origin: CGPoint(x: window.bounds.width / 2.0, y: window.bounds.size.height - 1.0), size: CGSize(width: 1.0, height: 1.0)) window.rootViewController?.present(controller, animated: true) + legacyController.presentationCompleted = nil } } } diff --git a/TelegramUI/OpenChatMessage.swift b/TelegramUI/OpenChatMessage.swift index ad73eef935..4f90f3b8d7 100644 --- a/TelegramUI/OpenChatMessage.swift +++ b/TelegramUI/OpenChatMessage.swift @@ -79,7 +79,7 @@ private func chatMessageGalleryControllerData(account: Account, message: Message if let navigationController = navigationController { navigationController.replaceTopController(controller, animated: false, ready: ready) } - }) + }, baseNavigationController: navigationController) return .instantPage(gallery, centralIndex, galleryMedia) } else if let galleryMedia = galleryMedia { if let mapMedia = galleryMedia as? TelegramMediaMap { diff --git a/TelegramUI/PhotoResources.swift b/TelegramUI/PhotoResources.swift index 613f8909b9..7c70282cc4 100644 --- a/TelegramUI/PhotoResources.swift +++ b/TelegramUI/PhotoResources.swift @@ -82,8 +82,8 @@ private func chatMessagePhotoDatas(postbox: Postbox, photoReference: ImageMediaR } } -private func chatMessageFileDatas(account: Account, fileReference: FileMediaReference, pathExtension: String? = nil, progressive: Bool = false) -> Signal<(Data?, String?, Bool), NoError> { - let thumbnailResource = smallestImageRepresentation(fileReference.media.previewRepresentations)?.resource +private func chatMessageFileDatas(account: Account, fileReference: FileMediaReference, pathExtension: String? = nil, progressive: Bool = false, fetched: Bool = false) -> Signal<(Data?, String?, Bool), NoError> { + let thumbnailResource = fetched ? nil : smallestImageRepresentation(fileReference.media.previewRepresentations)?.resource let fullSizeResource = fileReference.media.resource let maybeFullSize = account.postbox.mediaBox.resourceData(fullSizeResource, pathExtension: pathExtension) @@ -1737,12 +1737,12 @@ func drawImage(context: CGContext, image: CGImage, orientation: UIImageOrientati } } -func chatMessageImageFile(account: Account, fileReference: FileMediaReference, thumbnail: Bool) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { +func chatMessageImageFile(account: Account, fileReference: FileMediaReference, thumbnail: Bool, fetched: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { let signal: Signal<(Data?, String?, Bool), NoError> if thumbnail { signal = chatMessageImageFileThumbnailDatas(account: account, fileReference: fileReference) } else { - signal = chatMessageFileDatas(account: account, fileReference: fileReference, progressive: false) + signal = chatMessageFileDatas(account: account, fileReference: fileReference, progressive: false, fetched: fetched) } return signal @@ -1772,6 +1772,8 @@ func chatMessageImageFile(account: Account, fileReference: FileMediaReference, t if thumbnail { fittedSize = CGSize(width: CGFloat(image.width), height: CGFloat(image.height)).aspectFilled(arguments.boundingSize) } + } else { + print("wtf") } } } @@ -1802,7 +1804,7 @@ func chatMessageImageFile(account: Account, fileReference: FileMediaReference, t context.withFlippedContext { c in c.setBlendMode(.copy) - if arguments.boundingSize != fittedSize { + if arguments.boundingSize != fittedSize && !fetched { c.fill(drawingRect) } diff --git a/TelegramUI/PresentationStrings.swift b/TelegramUI/PresentationStrings.swift index 7821ff82d6..5c0f88660b 100644 --- a/TelegramUI/PresentationStrings.swift +++ b/TelegramUI/PresentationStrings.swift @@ -915,6 +915,7 @@ public final class PresentationStrings { } public let Channel_AdminLog_MessageRestrictedForever: String public let Passport_DeleteDocument: String + public let Notifications_ExceptionsResetToDefaults: String public let ChannelInfo_DeleteChannelConfirmation: String public let Passport_Address_OneOfTypeBankStatement: String public let Weekday_ShortSaturday: String @@ -1537,7 +1538,6 @@ public final class PresentationStrings { } public let Conversation_ClearPrivateHistory: String public let Conversation_ContextMenuShare: String - public let Notifications_ExceptionsResetToDefaults: String public let Notifications_ExceptionsNone: String private let _Time_MonthOfYear_m6: String private let _Time_MonthOfYear_m6_r: [(Int, NSRange)] @@ -2190,6 +2190,7 @@ public final class PresentationStrings { return formatWithArgumentRanges(_Channel_AdminLog_MessageRemovedGroupStickerPack, self._Channel_AdminLog_MessageRemovedGroupStickerPack_r, [_0]) } public let PrivacyPolicy_DeclineTitle: String + public let AuthSessions_PasswordPending: String public let AccessDenied_VideoMessageCamera: String public let Privacy_ContactsSyncHelp: String public let Conversation_Search: String @@ -3613,1016 +3614,26 @@ public final class PresentationStrings { public let PrivacySettings_PasscodeAndFaceId: String public let Settings_ChatBackground: String public let Login_TermsOfServiceDecline: String - private let _LastSeen_MinutesAgo_zero: String - private let _LastSeen_MinutesAgo_one: String - private let _LastSeen_MinutesAgo_two: String - private let _LastSeen_MinutesAgo_few: String - private let _LastSeen_MinutesAgo_many: String - private let _LastSeen_MinutesAgo_other: String - public func LastSeen_MinutesAgo(_ value: Int32) -> String { + private let _Conversation_StatusOnline_zero: String + private let _Conversation_StatusOnline_one: String + private let _Conversation_StatusOnline_two: String + private let _Conversation_StatusOnline_few: String + private let _Conversation_StatusOnline_many: String + private let _Conversation_StatusOnline_other: String + public func Conversation_StatusOnline(_ value: Int32) -> String { switch presentationStringsPluralizationForm(self.lc, value) { case .zero: - return String(format: self._LastSeen_MinutesAgo_zero, "\(value)") + return String(format: self._Conversation_StatusOnline_zero, "\(value)") case .one: - return String(format: self._LastSeen_MinutesAgo_one, "\(value)") + return String(format: self._Conversation_StatusOnline_one, "\(value)") case .two: - return String(format: self._LastSeen_MinutesAgo_two, "\(value)") + return String(format: self._Conversation_StatusOnline_two, "\(value)") case .few: - return String(format: self._LastSeen_MinutesAgo_few, "\(value)") + return String(format: self._Conversation_StatusOnline_few, "\(value)") case .many: - return String(format: self._LastSeen_MinutesAgo_many, "\(value)") + return String(format: self._Conversation_StatusOnline_many, "\(value)") case .other: - return String(format: self._LastSeen_MinutesAgo_other, "\(value)") - } - } - private let _Passport_Scans_zero: String - private let _Passport_Scans_one: String - private let _Passport_Scans_two: String - private let _Passport_Scans_few: String - private let _Passport_Scans_many: String - private let _Passport_Scans_other: String - public func Passport_Scans(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._Passport_Scans_zero, "\(value)") - case .one: - return String(format: self._Passport_Scans_one, "\(value)") - case .two: - return String(format: self._Passport_Scans_two, "\(value)") - case .few: - return String(format: self._Passport_Scans_few, "\(value)") - case .many: - return String(format: self._Passport_Scans_many, "\(value)") - case .other: - return String(format: self._Passport_Scans_other, "\(value)") - } - } - private let _Notifications_Exceptions_zero: String - private let _Notifications_Exceptions_one: String - private let _Notifications_Exceptions_two: String - private let _Notifications_Exceptions_few: String - private let _Notifications_Exceptions_many: String - private let _Notifications_Exceptions_other: String - public func Notifications_Exceptions(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._Notifications_Exceptions_zero, "\(value)") - case .one: - return String(format: self._Notifications_Exceptions_one, "\(value)") - case .two: - return String(format: self._Notifications_Exceptions_two, "\(value)") - case .few: - return String(format: self._Notifications_Exceptions_few, "\(value)") - case .many: - return String(format: self._Notifications_Exceptions_many, "\(value)") - case .other: - return String(format: self._Notifications_Exceptions_other, "\(value)") - } - } - private let _MuteExpires_Days_zero: String - private let _MuteExpires_Days_one: String - private let _MuteExpires_Days_two: String - private let _MuteExpires_Days_few: String - private let _MuteExpires_Days_many: String - private let _MuteExpires_Days_other: String - public func MuteExpires_Days(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._MuteExpires_Days_zero, "\(value)") - case .one: - return String(format: self._MuteExpires_Days_one, "\(value)") - case .two: - return String(format: self._MuteExpires_Days_two, "\(value)") - case .few: - return String(format: self._MuteExpires_Days_few, "\(value)") - case .many: - return String(format: self._MuteExpires_Days_many, "\(value)") - case .other: - return String(format: self._MuteExpires_Days_other, "\(value)") - } - } - private let _Watch_UserInfo_Mute_zero: String - private let _Watch_UserInfo_Mute_one: String - private let _Watch_UserInfo_Mute_two: String - private let _Watch_UserInfo_Mute_few: String - private let _Watch_UserInfo_Mute_many: String - private let _Watch_UserInfo_Mute_other: String - public func Watch_UserInfo_Mute(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._Watch_UserInfo_Mute_zero, "\(value)") - case .one: - return String(format: self._Watch_UserInfo_Mute_one, "\(value)") - case .two: - return String(format: self._Watch_UserInfo_Mute_two, "\(value)") - case .few: - return String(format: self._Watch_UserInfo_Mute_few, "\(value)") - case .many: - return String(format: self._Watch_UserInfo_Mute_many, "\(value)") - case .other: - return String(format: self._Watch_UserInfo_Mute_other, "\(value)") - } - } - private let _MessageTimer_Days_zero: String - private let _MessageTimer_Days_one: String - private let _MessageTimer_Days_two: String - private let _MessageTimer_Days_few: String - private let _MessageTimer_Days_many: String - private let _MessageTimer_Days_other: String - public func MessageTimer_Days(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._MessageTimer_Days_zero, "\(value)") - case .one: - return String(format: self._MessageTimer_Days_one, "\(value)") - case .two: - return String(format: self._MessageTimer_Days_two, "\(value)") - case .few: - return String(format: self._MessageTimer_Days_few, "\(value)") - case .many: - return String(format: self._MessageTimer_Days_many, "\(value)") - case .other: - return String(format: self._MessageTimer_Days_other, "\(value)") - } - } - private let _Map_ETAMinutes_zero: String - private let _Map_ETAMinutes_one: String - private let _Map_ETAMinutes_two: String - private let _Map_ETAMinutes_few: String - private let _Map_ETAMinutes_many: String - private let _Map_ETAMinutes_other: String - public func Map_ETAMinutes(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._Map_ETAMinutes_zero, "\(value)") - case .one: - return String(format: self._Map_ETAMinutes_one, "\(value)") - case .two: - return String(format: self._Map_ETAMinutes_two, "\(value)") - case .few: - return String(format: self._Map_ETAMinutes_few, "\(value)") - case .many: - return String(format: self._Map_ETAMinutes_many, "\(value)") - case .other: - return String(format: self._Map_ETAMinutes_other, "\(value)") - } - } - private let _Contacts_ImportersCount_zero: String - private let _Contacts_ImportersCount_one: String - private let _Contacts_ImportersCount_two: String - private let _Contacts_ImportersCount_few: String - private let _Contacts_ImportersCount_many: String - private let _Contacts_ImportersCount_other: String - public func Contacts_ImportersCount(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._Contacts_ImportersCount_zero, "\(value)") - case .one: - return String(format: self._Contacts_ImportersCount_one, "\(value)") - case .two: - return String(format: self._Contacts_ImportersCount_two, "\(value)") - case .few: - return String(format: self._Contacts_ImportersCount_few, "\(value)") - case .many: - return String(format: self._Contacts_ImportersCount_many, "\(value)") - case .other: - return String(format: self._Contacts_ImportersCount_other, "\(value)") - } - } - private let _StickerPack_AddStickerCount_zero: String - private let _StickerPack_AddStickerCount_one: String - private let _StickerPack_AddStickerCount_two: String - private let _StickerPack_AddStickerCount_few: String - private let _StickerPack_AddStickerCount_many: String - private let _StickerPack_AddStickerCount_other: String - public func StickerPack_AddStickerCount(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._StickerPack_AddStickerCount_zero, "\(value)") - case .one: - return String(format: self._StickerPack_AddStickerCount_one, "\(value)") - case .two: - return String(format: self._StickerPack_AddStickerCount_two, "\(value)") - case .few: - return String(format: self._StickerPack_AddStickerCount_few, "\(value)") - case .many: - return String(format: self._StickerPack_AddStickerCount_many, "\(value)") - case .other: - return String(format: self._StickerPack_AddStickerCount_other, "\(value)") - } - } - private let _UserCount_zero: String - private let _UserCount_one: String - private let _UserCount_two: String - private let _UserCount_few: String - private let _UserCount_many: String - private let _UserCount_other: String - public func UserCount(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._UserCount_zero, "\(value)") - case .one: - return String(format: self._UserCount_one, "\(value)") - case .two: - return String(format: self._UserCount_two, "\(value)") - case .few: - return String(format: self._UserCount_few, "\(value)") - case .many: - return String(format: self._UserCount_many, "\(value)") - case .other: - return String(format: self._UserCount_other, "\(value)") - } - } - private let _Notifications_ExceptionMuteExpires_Days_zero: String - private let _Notifications_ExceptionMuteExpires_Days_one: String - private let _Notifications_ExceptionMuteExpires_Days_two: String - private let _Notifications_ExceptionMuteExpires_Days_few: String - private let _Notifications_ExceptionMuteExpires_Days_many: String - private let _Notifications_ExceptionMuteExpires_Days_other: String - public func Notifications_ExceptionMuteExpires_Days(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._Notifications_ExceptionMuteExpires_Days_zero, "\(value)") - case .one: - return String(format: self._Notifications_ExceptionMuteExpires_Days_one, "\(value)") - case .two: - return String(format: self._Notifications_ExceptionMuteExpires_Days_two, "\(value)") - case .few: - return String(format: self._Notifications_ExceptionMuteExpires_Days_few, "\(value)") - case .many: - return String(format: self._Notifications_ExceptionMuteExpires_Days_many, "\(value)") - case .other: - return String(format: self._Notifications_ExceptionMuteExpires_Days_other, "\(value)") - } - } - private let _MuteFor_Hours_zero: String - private let _MuteFor_Hours_one: String - private let _MuteFor_Hours_two: String - private let _MuteFor_Hours_few: String - private let _MuteFor_Hours_many: String - private let _MuteFor_Hours_other: String - public func MuteFor_Hours(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._MuteFor_Hours_zero, "\(value)") - case .one: - return String(format: self._MuteFor_Hours_one, "\(value)") - case .two: - return String(format: self._MuteFor_Hours_two, "\(value)") - case .few: - return String(format: self._MuteFor_Hours_few, "\(value)") - case .many: - return String(format: self._MuteFor_Hours_many, "\(value)") - case .other: - return String(format: self._MuteFor_Hours_other, "\(value)") - } - } - private let _MessageTimer_Hours_zero: String - private let _MessageTimer_Hours_one: String - private let _MessageTimer_Hours_two: String - private let _MessageTimer_Hours_few: String - private let _MessageTimer_Hours_many: String - private let _MessageTimer_Hours_other: String - public func MessageTimer_Hours(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._MessageTimer_Hours_zero, "\(value)") - case .one: - return String(format: self._MessageTimer_Hours_one, "\(value)") - case .two: - return String(format: self._MessageTimer_Hours_two, "\(value)") - case .few: - return String(format: self._MessageTimer_Hours_few, "\(value)") - case .many: - return String(format: self._MessageTimer_Hours_many, "\(value)") - case .other: - return String(format: self._MessageTimer_Hours_other, "\(value)") - } - } - private let _Media_SharePhoto_zero: String - private let _Media_SharePhoto_one: String - private let _Media_SharePhoto_two: String - private let _Media_SharePhoto_few: String - private let _Media_SharePhoto_many: String - private let _Media_SharePhoto_other: String - public func Media_SharePhoto(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._Media_SharePhoto_zero, "\(value)") - case .one: - return String(format: self._Media_SharePhoto_one, "\(value)") - case .two: - return String(format: self._Media_SharePhoto_two, "\(value)") - case .few: - return String(format: self._Media_SharePhoto_few, "\(value)") - case .many: - return String(format: self._Media_SharePhoto_many, "\(value)") - case .other: - return String(format: self._Media_SharePhoto_other, "\(value)") - } - } - private let _Map_ETAHours_zero: String - private let _Map_ETAHours_one: String - private let _Map_ETAHours_two: String - private let _Map_ETAHours_few: String - private let _Map_ETAHours_many: String - private let _Map_ETAHours_other: String - public func Map_ETAHours(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._Map_ETAHours_zero, "\(value)") - case .one: - return String(format: self._Map_ETAHours_one, "\(value)") - case .two: - return String(format: self._Map_ETAHours_two, "\(value)") - case .few: - return String(format: self._Map_ETAHours_few, "\(value)") - case .many: - return String(format: self._Map_ETAHours_many, "\(value)") - case .other: - return String(format: self._Map_ETAHours_other, "\(value)") - } - } - private let _Conversation_StatusMembers_zero: String - private let _Conversation_StatusMembers_one: String - private let _Conversation_StatusMembers_two: String - private let _Conversation_StatusMembers_few: String - private let _Conversation_StatusMembers_many: String - private let _Conversation_StatusMembers_other: String - public func Conversation_StatusMembers(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._Conversation_StatusMembers_zero, "\(value)") - case .one: - return String(format: self._Conversation_StatusMembers_one, "\(value)") - case .two: - return String(format: self._Conversation_StatusMembers_two, "\(value)") - case .few: - return String(format: self._Conversation_StatusMembers_few, "\(value)") - case .many: - return String(format: self._Conversation_StatusMembers_many, "\(value)") - case .other: - return String(format: self._Conversation_StatusMembers_other, "\(value)") - } - } - private let _ServiceMessage_GameScoreSelfExtended_zero: String - private let _ServiceMessage_GameScoreSelfExtended_one: String - private let _ServiceMessage_GameScoreSelfExtended_two: String - private let _ServiceMessage_GameScoreSelfExtended_few: String - private let _ServiceMessage_GameScoreSelfExtended_many: String - private let _ServiceMessage_GameScoreSelfExtended_other: String - public func ServiceMessage_GameScoreSelfExtended(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._ServiceMessage_GameScoreSelfExtended_zero, "\(value)") - case .one: - return String(format: self._ServiceMessage_GameScoreSelfExtended_one, "\(value)") - case .two: - return String(format: self._ServiceMessage_GameScoreSelfExtended_two, "\(value)") - case .few: - return String(format: self._ServiceMessage_GameScoreSelfExtended_few, "\(value)") - case .many: - return String(format: self._ServiceMessage_GameScoreSelfExtended_many, "\(value)") - case .other: - return String(format: self._ServiceMessage_GameScoreSelfExtended_other, "\(value)") - } - } - private let _SharedMedia_Link_zero: String - private let _SharedMedia_Link_one: String - private let _SharedMedia_Link_two: String - private let _SharedMedia_Link_few: String - private let _SharedMedia_Link_many: String - private let _SharedMedia_Link_other: String - public func SharedMedia_Link(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._SharedMedia_Link_zero, "\(value)") - case .one: - return String(format: self._SharedMedia_Link_one, "\(value)") - case .two: - return String(format: self._SharedMedia_Link_two, "\(value)") - case .few: - return String(format: self._SharedMedia_Link_few, "\(value)") - case .many: - return String(format: self._SharedMedia_Link_many, "\(value)") - case .other: - return String(format: self._SharedMedia_Link_other, "\(value)") - } - } - private let _Media_ShareItem_zero: String - private let _Media_ShareItem_one: String - private let _Media_ShareItem_two: String - private let _Media_ShareItem_few: String - private let _Media_ShareItem_many: String - private let _Media_ShareItem_other: String - public func Media_ShareItem(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._Media_ShareItem_zero, "\(value)") - case .one: - return String(format: self._Media_ShareItem_one, "\(value)") - case .two: - return String(format: self._Media_ShareItem_two, "\(value)") - case .few: - return String(format: self._Media_ShareItem_few, "\(value)") - case .many: - return String(format: self._Media_ShareItem_many, "\(value)") - case .other: - return String(format: self._Media_ShareItem_other, "\(value)") - } - } - private let _PasscodeSettings_FailedAttempts_zero: String - private let _PasscodeSettings_FailedAttempts_one: String - private let _PasscodeSettings_FailedAttempts_two: String - private let _PasscodeSettings_FailedAttempts_few: String - private let _PasscodeSettings_FailedAttempts_many: String - private let _PasscodeSettings_FailedAttempts_other: String - public func PasscodeSettings_FailedAttempts(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._PasscodeSettings_FailedAttempts_zero, "\(value)") - case .one: - return String(format: self._PasscodeSettings_FailedAttempts_one, "\(value)") - case .two: - return String(format: self._PasscodeSettings_FailedAttempts_two, "\(value)") - case .few: - return String(format: self._PasscodeSettings_FailedAttempts_few, "\(value)") - case .many: - return String(format: self._PasscodeSettings_FailedAttempts_many, "\(value)") - case .other: - return String(format: self._PasscodeSettings_FailedAttempts_other, "\(value)") - } - } - private let _Notification_GameScoreSelfSimple_zero: String - private let _Notification_GameScoreSelfSimple_one: String - private let _Notification_GameScoreSelfSimple_two: String - private let _Notification_GameScoreSelfSimple_few: String - private let _Notification_GameScoreSelfSimple_many: String - private let _Notification_GameScoreSelfSimple_other: String - public func Notification_GameScoreSelfSimple(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._Notification_GameScoreSelfSimple_zero, "\(value)") - case .one: - return String(format: self._Notification_GameScoreSelfSimple_one, "\(value)") - case .two: - return String(format: self._Notification_GameScoreSelfSimple_two, "\(value)") - case .few: - return String(format: self._Notification_GameScoreSelfSimple_few, "\(value)") - case .many: - return String(format: self._Notification_GameScoreSelfSimple_many, "\(value)") - case .other: - return String(format: self._Notification_GameScoreSelfSimple_other, "\(value)") - } - } - private let _Notifications_ExceptionMuteExpires_Hours_zero: String - private let _Notifications_ExceptionMuteExpires_Hours_one: String - private let _Notifications_ExceptionMuteExpires_Hours_two: String - private let _Notifications_ExceptionMuteExpires_Hours_few: String - private let _Notifications_ExceptionMuteExpires_Hours_many: String - private let _Notifications_ExceptionMuteExpires_Hours_other: String - public func Notifications_ExceptionMuteExpires_Hours(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._Notifications_ExceptionMuteExpires_Hours_zero, "\(value)") - case .one: - return String(format: self._Notifications_ExceptionMuteExpires_Hours_one, "\(value)") - case .two: - return String(format: self._Notifications_ExceptionMuteExpires_Hours_two, "\(value)") - case .few: - return String(format: self._Notifications_ExceptionMuteExpires_Hours_few, "\(value)") - case .many: - return String(format: self._Notifications_ExceptionMuteExpires_Hours_many, "\(value)") - case .other: - return String(format: self._Notifications_ExceptionMuteExpires_Hours_other, "\(value)") - } - } - private let _LastSeen_HoursAgo_zero: String - private let _LastSeen_HoursAgo_one: String - private let _LastSeen_HoursAgo_two: String - private let _LastSeen_HoursAgo_few: String - private let _LastSeen_HoursAgo_many: String - private let _LastSeen_HoursAgo_other: String - public func LastSeen_HoursAgo(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._LastSeen_HoursAgo_zero, "\(value)") - case .one: - return String(format: self._LastSeen_HoursAgo_one, "\(value)") - case .two: - return String(format: self._LastSeen_HoursAgo_two, "\(value)") - case .few: - return String(format: self._LastSeen_HoursAgo_few, "\(value)") - case .many: - return String(format: self._LastSeen_HoursAgo_many, "\(value)") - case .other: - return String(format: self._LastSeen_HoursAgo_other, "\(value)") - } - } - private let _MessageTimer_Seconds_zero: String - private let _MessageTimer_Seconds_one: String - private let _MessageTimer_Seconds_two: String - private let _MessageTimer_Seconds_few: String - private let _MessageTimer_Seconds_many: String - private let _MessageTimer_Seconds_other: String - public func MessageTimer_Seconds(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._MessageTimer_Seconds_zero, "\(value)") - case .one: - return String(format: self._MessageTimer_Seconds_one, "\(value)") - case .two: - return String(format: self._MessageTimer_Seconds_two, "\(value)") - case .few: - return String(format: self._MessageTimer_Seconds_few, "\(value)") - case .many: - return String(format: self._MessageTimer_Seconds_many, "\(value)") - case .other: - return String(format: self._MessageTimer_Seconds_other, "\(value)") - } - } - private let _Notification_GameScoreSelfExtended_zero: String - private let _Notification_GameScoreSelfExtended_one: String - private let _Notification_GameScoreSelfExtended_two: String - private let _Notification_GameScoreSelfExtended_few: String - private let _Notification_GameScoreSelfExtended_many: String - private let _Notification_GameScoreSelfExtended_other: String - public func Notification_GameScoreSelfExtended(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._Notification_GameScoreSelfExtended_zero, "\(value)") - case .one: - return String(format: self._Notification_GameScoreSelfExtended_one, "\(value)") - case .two: - return String(format: self._Notification_GameScoreSelfExtended_two, "\(value)") - case .few: - return String(format: self._Notification_GameScoreSelfExtended_few, "\(value)") - case .many: - return String(format: self._Notification_GameScoreSelfExtended_many, "\(value)") - case .other: - return String(format: self._Notification_GameScoreSelfExtended_other, "\(value)") - } - } - private let _Notification_GameScoreSimple_zero: String - private let _Notification_GameScoreSimple_one: String - private let _Notification_GameScoreSimple_two: String - private let _Notification_GameScoreSimple_few: String - private let _Notification_GameScoreSimple_many: String - private let _Notification_GameScoreSimple_other: String - public func Notification_GameScoreSimple(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._Notification_GameScoreSimple_zero, "\(value)") - case .one: - return String(format: self._Notification_GameScoreSimple_one, "\(value)") - case .two: - return String(format: self._Notification_GameScoreSimple_two, "\(value)") - case .few: - return String(format: self._Notification_GameScoreSimple_few, "\(value)") - case .many: - return String(format: self._Notification_GameScoreSimple_many, "\(value)") - case .other: - return String(format: self._Notification_GameScoreSimple_other, "\(value)") - } - } - private let _MuteFor_Days_zero: String - private let _MuteFor_Days_one: String - private let _MuteFor_Days_two: String - private let _MuteFor_Days_few: String - private let _MuteFor_Days_many: String - private let _MuteFor_Days_other: String - public func MuteFor_Days(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._MuteFor_Days_zero, "\(value)") - case .one: - return String(format: self._MuteFor_Days_one, "\(value)") - case .two: - return String(format: self._MuteFor_Days_two, "\(value)") - case .few: - return String(format: self._MuteFor_Days_few, "\(value)") - case .many: - return String(format: self._MuteFor_Days_many, "\(value)") - case .other: - return String(format: self._MuteFor_Days_other, "\(value)") - } - } - private let _Conversation_StatusSubscribers_zero: String - private let _Conversation_StatusSubscribers_one: String - private let _Conversation_StatusSubscribers_two: String - private let _Conversation_StatusSubscribers_few: String - private let _Conversation_StatusSubscribers_many: String - private let _Conversation_StatusSubscribers_other: String - public func Conversation_StatusSubscribers(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._Conversation_StatusSubscribers_zero, "\(value)") - case .one: - return String(format: self._Conversation_StatusSubscribers_one, "\(value)") - case .two: - return String(format: self._Conversation_StatusSubscribers_two, "\(value)") - case .few: - return String(format: self._Conversation_StatusSubscribers_few, "\(value)") - case .many: - return String(format: self._Conversation_StatusSubscribers_many, "\(value)") - case .other: - return String(format: self._Conversation_StatusSubscribers_other, "\(value)") - } - } - private let _ForwardedGifs_zero: String - private let _ForwardedGifs_one: String - private let _ForwardedGifs_two: String - private let _ForwardedGifs_few: String - private let _ForwardedGifs_many: String - private let _ForwardedGifs_other: String - public func ForwardedGifs(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._ForwardedGifs_zero, "\(value)") - case .one: - return String(format: self._ForwardedGifs_one, "\(value)") - case .two: - return String(format: self._ForwardedGifs_two, "\(value)") - case .few: - return String(format: self._ForwardedGifs_few, "\(value)") - case .many: - return String(format: self._ForwardedGifs_many, "\(value)") - case .other: - return String(format: self._ForwardedGifs_other, "\(value)") - } - } - private let _StickerPack_AddMaskCount_zero: String - private let _StickerPack_AddMaskCount_one: String - private let _StickerPack_AddMaskCount_two: String - private let _StickerPack_AddMaskCount_few: String - private let _StickerPack_AddMaskCount_many: String - private let _StickerPack_AddMaskCount_other: String - public func StickerPack_AddMaskCount(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._StickerPack_AddMaskCount_zero, "\(value)") - case .one: - return String(format: self._StickerPack_AddMaskCount_one, "\(value)") - case .two: - return String(format: self._StickerPack_AddMaskCount_two, "\(value)") - case .few: - return String(format: self._StickerPack_AddMaskCount_few, "\(value)") - case .many: - return String(format: self._StickerPack_AddMaskCount_many, "\(value)") - case .other: - return String(format: self._StickerPack_AddMaskCount_other, "\(value)") - } - } - private let _MessageTimer_ShortSeconds_zero: String - private let _MessageTimer_ShortSeconds_one: String - private let _MessageTimer_ShortSeconds_two: String - private let _MessageTimer_ShortSeconds_few: String - private let _MessageTimer_ShortSeconds_many: String - private let _MessageTimer_ShortSeconds_other: String - public func MessageTimer_ShortSeconds(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._MessageTimer_ShortSeconds_zero, "\(value)") - case .one: - return String(format: self._MessageTimer_ShortSeconds_one, "\(value)") - case .two: - return String(format: self._MessageTimer_ShortSeconds_two, "\(value)") - case .few: - return String(format: self._MessageTimer_ShortSeconds_few, "\(value)") - case .many: - return String(format: self._MessageTimer_ShortSeconds_many, "\(value)") - case .other: - return String(format: self._MessageTimer_ShortSeconds_other, "\(value)") - } - } - private let _AttachmentMenu_SendPhoto_zero: String - private let _AttachmentMenu_SendPhoto_one: String - private let _AttachmentMenu_SendPhoto_two: String - private let _AttachmentMenu_SendPhoto_few: String - private let _AttachmentMenu_SendPhoto_many: String - private let _AttachmentMenu_SendPhoto_other: String - public func AttachmentMenu_SendPhoto(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._AttachmentMenu_SendPhoto_zero, "\(value)") - case .one: - return String(format: self._AttachmentMenu_SendPhoto_one, "\(value)") - case .two: - return String(format: self._AttachmentMenu_SendPhoto_two, "\(value)") - case .few: - return String(format: self._AttachmentMenu_SendPhoto_few, "\(value)") - case .many: - return String(format: self._AttachmentMenu_SendPhoto_many, "\(value)") - case .other: - return String(format: self._AttachmentMenu_SendPhoto_other, "\(value)") - } - } - private let _Notifications_ExceptionMuteExpires_Minutes_zero: String - private let _Notifications_ExceptionMuteExpires_Minutes_one: String - private let _Notifications_ExceptionMuteExpires_Minutes_two: String - private let _Notifications_ExceptionMuteExpires_Minutes_few: String - private let _Notifications_ExceptionMuteExpires_Minutes_many: String - private let _Notifications_ExceptionMuteExpires_Minutes_other: String - public func Notifications_ExceptionMuteExpires_Minutes(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._Notifications_ExceptionMuteExpires_Minutes_zero, "\(value)") - case .one: - return String(format: self._Notifications_ExceptionMuteExpires_Minutes_one, "\(value)") - case .two: - return String(format: self._Notifications_ExceptionMuteExpires_Minutes_two, "\(value)") - case .few: - return String(format: self._Notifications_ExceptionMuteExpires_Minutes_few, "\(value)") - case .many: - return String(format: self._Notifications_ExceptionMuteExpires_Minutes_many, "\(value)") - case .other: - return String(format: self._Notifications_ExceptionMuteExpires_Minutes_other, "\(value)") - } - } - private let _MessageTimer_ShortWeeks_zero: String - private let _MessageTimer_ShortWeeks_one: String - private let _MessageTimer_ShortWeeks_two: String - private let _MessageTimer_ShortWeeks_few: String - private let _MessageTimer_ShortWeeks_many: String - private let _MessageTimer_ShortWeeks_other: String - public func MessageTimer_ShortWeeks(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._MessageTimer_ShortWeeks_zero, "\(value)") - case .one: - return String(format: self._MessageTimer_ShortWeeks_one, "\(value)") - case .two: - return String(format: self._MessageTimer_ShortWeeks_two, "\(value)") - case .few: - return String(format: self._MessageTimer_ShortWeeks_few, "\(value)") - case .many: - return String(format: self._MessageTimer_ShortWeeks_many, "\(value)") - case .other: - return String(format: self._MessageTimer_ShortWeeks_other, "\(value)") - } - } - private let _ForwardedStickers_zero: String - private let _ForwardedStickers_one: String - private let _ForwardedStickers_two: String - private let _ForwardedStickers_few: String - private let _ForwardedStickers_many: String - private let _ForwardedStickers_other: String - public func ForwardedStickers(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._ForwardedStickers_zero, "\(value)") - case .one: - return String(format: self._ForwardedStickers_one, "\(value)") - case .two: - return String(format: self._ForwardedStickers_two, "\(value)") - case .few: - return String(format: self._ForwardedStickers_few, "\(value)") - case .many: - return String(format: self._ForwardedStickers_many, "\(value)") - case .other: - return String(format: self._ForwardedStickers_other, "\(value)") - } - } - private let _Forward_ConfirmMultipleFiles_zero: String - private let _Forward_ConfirmMultipleFiles_one: String - private let _Forward_ConfirmMultipleFiles_two: String - private let _Forward_ConfirmMultipleFiles_few: String - private let _Forward_ConfirmMultipleFiles_many: String - private let _Forward_ConfirmMultipleFiles_other: String - public func Forward_ConfirmMultipleFiles(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._Forward_ConfirmMultipleFiles_zero, "\(value)") - case .one: - return String(format: self._Forward_ConfirmMultipleFiles_one, "\(value)") - case .two: - return String(format: self._Forward_ConfirmMultipleFiles_two, "\(value)") - case .few: - return String(format: self._Forward_ConfirmMultipleFiles_few, "\(value)") - case .many: - return String(format: self._Forward_ConfirmMultipleFiles_many, "\(value)") - case .other: - return String(format: self._Forward_ConfirmMultipleFiles_other, "\(value)") - } - } - private let _SharedMedia_Photo_zero: String - private let _SharedMedia_Photo_one: String - private let _SharedMedia_Photo_two: String - private let _SharedMedia_Photo_few: String - private let _SharedMedia_Photo_many: String - private let _SharedMedia_Photo_other: String - public func SharedMedia_Photo(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._SharedMedia_Photo_zero, "\(value)") - case .one: - return String(format: self._SharedMedia_Photo_one, "\(value)") - case .two: - return String(format: self._SharedMedia_Photo_two, "\(value)") - case .few: - return String(format: self._SharedMedia_Photo_few, "\(value)") - case .many: - return String(format: self._SharedMedia_Photo_many, "\(value)") - case .other: - return String(format: self._SharedMedia_Photo_other, "\(value)") - } - } - private let _StickerPack_RemoveStickerCount_zero: String - private let _StickerPack_RemoveStickerCount_one: String - private let _StickerPack_RemoveStickerCount_two: String - private let _StickerPack_RemoveStickerCount_few: String - private let _StickerPack_RemoveStickerCount_many: String - private let _StickerPack_RemoveStickerCount_other: String - public func StickerPack_RemoveStickerCount(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._StickerPack_RemoveStickerCount_zero, "\(value)") - case .one: - return String(format: self._StickerPack_RemoveStickerCount_one, "\(value)") - case .two: - return String(format: self._StickerPack_RemoveStickerCount_two, "\(value)") - case .few: - return String(format: self._StickerPack_RemoveStickerCount_few, "\(value)") - case .many: - return String(format: self._StickerPack_RemoveStickerCount_many, "\(value)") - case .other: - return String(format: self._StickerPack_RemoveStickerCount_other, "\(value)") - } - } - private let _InviteText_ContactsCountText_zero: String - private let _InviteText_ContactsCountText_one: String - private let _InviteText_ContactsCountText_two: String - private let _InviteText_ContactsCountText_few: String - private let _InviteText_ContactsCountText_many: String - private let _InviteText_ContactsCountText_other: String - public func InviteText_ContactsCountText(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._InviteText_ContactsCountText_zero, "\(value)") - case .one: - return String(format: self._InviteText_ContactsCountText_one, "\(value)") - case .two: - return String(format: self._InviteText_ContactsCountText_two, "\(value)") - case .few: - return String(format: self._InviteText_ContactsCountText_few, "\(value)") - case .many: - return String(format: self._InviteText_ContactsCountText_many, "\(value)") - case .other: - return String(format: self._InviteText_ContactsCountText_other, "\(value)") - } - } - private let _MuteExpires_Hours_zero: String - private let _MuteExpires_Hours_one: String - private let _MuteExpires_Hours_two: String - private let _MuteExpires_Hours_few: String - private let _MuteExpires_Hours_many: String - private let _MuteExpires_Hours_other: String - public func MuteExpires_Hours(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._MuteExpires_Hours_zero, "\(value)") - case .one: - return String(format: self._MuteExpires_Hours_one, "\(value)") - case .two: - return String(format: self._MuteExpires_Hours_two, "\(value)") - case .few: - return String(format: self._MuteExpires_Hours_few, "\(value)") - case .many: - return String(format: self._MuteExpires_Hours_many, "\(value)") - case .other: - return String(format: self._MuteExpires_Hours_other, "\(value)") - } - } - private let _Notification_GameScoreExtended_zero: String - private let _Notification_GameScoreExtended_one: String - private let _Notification_GameScoreExtended_two: String - private let _Notification_GameScoreExtended_few: String - private let _Notification_GameScoreExtended_many: String - private let _Notification_GameScoreExtended_other: String - public func Notification_GameScoreExtended(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._Notification_GameScoreExtended_zero, "\(value)") - case .one: - return String(format: self._Notification_GameScoreExtended_one, "\(value)") - case .two: - return String(format: self._Notification_GameScoreExtended_two, "\(value)") - case .few: - return String(format: self._Notification_GameScoreExtended_few, "\(value)") - case .many: - return String(format: self._Notification_GameScoreExtended_many, "\(value)") - case .other: - return String(format: self._Notification_GameScoreExtended_other, "\(value)") - } - } - private let _MuteExpires_Minutes_zero: String - private let _MuteExpires_Minutes_one: String - private let _MuteExpires_Minutes_two: String - private let _MuteExpires_Minutes_few: String - private let _MuteExpires_Minutes_many: String - private let _MuteExpires_Minutes_other: String - public func MuteExpires_Minutes(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._MuteExpires_Minutes_zero, "\(value)") - case .one: - return String(format: self._MuteExpires_Minutes_one, "\(value)") - case .two: - return String(format: self._MuteExpires_Minutes_two, "\(value)") - case .few: - return String(format: self._MuteExpires_Minutes_few, "\(value)") - case .many: - return String(format: self._MuteExpires_Minutes_many, "\(value)") - case .other: - return String(format: self._MuteExpires_Minutes_other, "\(value)") - } - } - private let _AttachmentMenu_SendItem_zero: String - private let _AttachmentMenu_SendItem_one: String - private let _AttachmentMenu_SendItem_two: String - private let _AttachmentMenu_SendItem_few: String - private let _AttachmentMenu_SendItem_many: String - private let _AttachmentMenu_SendItem_other: String - public func AttachmentMenu_SendItem(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._AttachmentMenu_SendItem_zero, "\(value)") - case .one: - return String(format: self._AttachmentMenu_SendItem_one, "\(value)") - case .two: - return String(format: self._AttachmentMenu_SendItem_two, "\(value)") - case .few: - return String(format: self._AttachmentMenu_SendItem_few, "\(value)") - case .many: - return String(format: self._AttachmentMenu_SendItem_many, "\(value)") - case .other: - return String(format: self._AttachmentMenu_SendItem_other, "\(value)") - } - } - private let _ForwardedAudios_zero: String - private let _ForwardedAudios_one: String - private let _ForwardedAudios_two: String - private let _ForwardedAudios_few: String - private let _ForwardedAudios_many: String - private let _ForwardedAudios_other: String - public func ForwardedAudios(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._ForwardedAudios_zero, "\(value)") - case .one: - return String(format: self._ForwardedAudios_one, "\(value)") - case .two: - return String(format: self._ForwardedAudios_two, "\(value)") - case .few: - return String(format: self._ForwardedAudios_few, "\(value)") - case .many: - return String(format: self._ForwardedAudios_many, "\(value)") - case .other: - return String(format: self._ForwardedAudios_other, "\(value)") - } - } - private let _SharedMedia_File_zero: String - private let _SharedMedia_File_one: String - private let _SharedMedia_File_two: String - private let _SharedMedia_File_few: String - private let _SharedMedia_File_many: String - private let _SharedMedia_File_other: String - public func SharedMedia_File(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._SharedMedia_File_zero, "\(value)") - case .one: - return String(format: self._SharedMedia_File_one, "\(value)") - case .two: - return String(format: self._SharedMedia_File_two, "\(value)") - case .few: - return String(format: self._SharedMedia_File_few, "\(value)") - case .many: - return String(format: self._SharedMedia_File_many, "\(value)") - case .other: - return String(format: self._SharedMedia_File_other, "\(value)") - } - } - private let _Call_ShortMinutes_zero: String - private let _Call_ShortMinutes_one: String - private let _Call_ShortMinutes_two: String - private let _Call_ShortMinutes_few: String - private let _Call_ShortMinutes_many: String - private let _Call_ShortMinutes_other: String - public func Call_ShortMinutes(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._Call_ShortMinutes_zero, "\(value)") - case .one: - return String(format: self._Call_ShortMinutes_one, "\(value)") - case .two: - return String(format: self._Call_ShortMinutes_two, "\(value)") - case .few: - return String(format: self._Call_ShortMinutes_few, "\(value)") - case .many: - return String(format: self._Call_ShortMinutes_many, "\(value)") - case .other: - return String(format: self._Call_ShortMinutes_other, "\(value)") + return String(format: self._Conversation_StatusOnline_other, "\(value)") } } private let _Call_Minutes_zero: String @@ -4647,642 +3658,26 @@ public final class PresentationStrings { return String(format: self._Call_Minutes_other, "\(value)") } } - private let _Call_ShortSeconds_zero: String - private let _Call_ShortSeconds_one: String - private let _Call_ShortSeconds_two: String - private let _Call_ShortSeconds_few: String - private let _Call_ShortSeconds_many: String - private let _Call_ShortSeconds_other: String - public func Call_ShortSeconds(_ value: Int32) -> String { + private let _GroupInfo_ParticipantCount_zero: String + private let _GroupInfo_ParticipantCount_one: String + private let _GroupInfo_ParticipantCount_two: String + private let _GroupInfo_ParticipantCount_few: String + private let _GroupInfo_ParticipantCount_many: String + private let _GroupInfo_ParticipantCount_other: String + public func GroupInfo_ParticipantCount(_ value: Int32) -> String { switch presentationStringsPluralizationForm(self.lc, value) { case .zero: - return String(format: self._Call_ShortSeconds_zero, "\(value)") + return String(format: self._GroupInfo_ParticipantCount_zero, "\(value)") case .one: - return String(format: self._Call_ShortSeconds_one, "\(value)") + return String(format: self._GroupInfo_ParticipantCount_one, "\(value)") case .two: - return String(format: self._Call_ShortSeconds_two, "\(value)") + return String(format: self._GroupInfo_ParticipantCount_two, "\(value)") case .few: - return String(format: self._Call_ShortSeconds_few, "\(value)") + return String(format: self._GroupInfo_ParticipantCount_few, "\(value)") case .many: - return String(format: self._Call_ShortSeconds_many, "\(value)") + return String(format: self._GroupInfo_ParticipantCount_many, "\(value)") case .other: - return String(format: self._Call_ShortSeconds_other, "\(value)") - } - } - private let _Invitation_Members_zero: String - private let _Invitation_Members_one: String - private let _Invitation_Members_two: String - private let _Invitation_Members_few: String - private let _Invitation_Members_many: String - private let _Invitation_Members_other: String - public func Invitation_Members(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._Invitation_Members_zero, "\(value)") - case .one: - return String(format: self._Invitation_Members_one, "\(value)") - case .two: - return String(format: self._Invitation_Members_two, "\(value)") - case .few: - return String(format: self._Invitation_Members_few, "\(value)") - case .many: - return String(format: self._Invitation_Members_many, "\(value)") - case .other: - return String(format: self._Invitation_Members_other, "\(value)") - } - } - private let _AttachmentMenu_SendGif_zero: String - private let _AttachmentMenu_SendGif_one: String - private let _AttachmentMenu_SendGif_two: String - private let _AttachmentMenu_SendGif_few: String - private let _AttachmentMenu_SendGif_many: String - private let _AttachmentMenu_SendGif_other: String - public func AttachmentMenu_SendGif(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._AttachmentMenu_SendGif_zero, "\(value)") - case .one: - return String(format: self._AttachmentMenu_SendGif_one, "\(value)") - case .two: - return String(format: self._AttachmentMenu_SendGif_two, "\(value)") - case .few: - return String(format: self._AttachmentMenu_SendGif_few, "\(value)") - case .many: - return String(format: self._AttachmentMenu_SendGif_many, "\(value)") - case .other: - return String(format: self._AttachmentMenu_SendGif_other, "\(value)") - } - } - private let _Conversation_LiveLocationMembersCount_zero: String - private let _Conversation_LiveLocationMembersCount_one: String - private let _Conversation_LiveLocationMembersCount_two: String - private let _Conversation_LiveLocationMembersCount_few: String - private let _Conversation_LiveLocationMembersCount_many: String - private let _Conversation_LiveLocationMembersCount_other: String - public func Conversation_LiveLocationMembersCount(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._Conversation_LiveLocationMembersCount_zero, "\(value)") - case .one: - return String(format: self._Conversation_LiveLocationMembersCount_one, "\(value)") - case .two: - return String(format: self._Conversation_LiveLocationMembersCount_two, "\(value)") - case .few: - return String(format: self._Conversation_LiveLocationMembersCount_few, "\(value)") - case .many: - return String(format: self._Conversation_LiveLocationMembersCount_many, "\(value)") - case .other: - return String(format: self._Conversation_LiveLocationMembersCount_other, "\(value)") - } - } - private let _Watch_LastSeen_MinutesAgo_zero: String - private let _Watch_LastSeen_MinutesAgo_one: String - private let _Watch_LastSeen_MinutesAgo_two: String - private let _Watch_LastSeen_MinutesAgo_few: String - private let _Watch_LastSeen_MinutesAgo_many: String - private let _Watch_LastSeen_MinutesAgo_other: String - public func Watch_LastSeen_MinutesAgo(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._Watch_LastSeen_MinutesAgo_zero, "\(value)") - case .one: - return String(format: self._Watch_LastSeen_MinutesAgo_one, "\(value)") - case .two: - return String(format: self._Watch_LastSeen_MinutesAgo_two, "\(value)") - case .few: - return String(format: self._Watch_LastSeen_MinutesAgo_few, "\(value)") - case .many: - return String(format: self._Watch_LastSeen_MinutesAgo_many, "\(value)") - case .other: - return String(format: self._Watch_LastSeen_MinutesAgo_other, "\(value)") - } - } - private let _MessageTimer_Months_zero: String - private let _MessageTimer_Months_one: String - private let _MessageTimer_Months_two: String - private let _MessageTimer_Months_few: String - private let _MessageTimer_Months_many: String - private let _MessageTimer_Months_other: String - public func MessageTimer_Months(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._MessageTimer_Months_zero, "\(value)") - case .one: - return String(format: self._MessageTimer_Months_one, "\(value)") - case .two: - return String(format: self._MessageTimer_Months_two, "\(value)") - case .few: - return String(format: self._MessageTimer_Months_few, "\(value)") - case .many: - return String(format: self._MessageTimer_Months_many, "\(value)") - case .other: - return String(format: self._MessageTimer_Months_other, "\(value)") - } - } - private let _ForwardedMessages_zero: String - private let _ForwardedMessages_one: String - private let _ForwardedMessages_two: String - private let _ForwardedMessages_few: String - private let _ForwardedMessages_many: String - private let _ForwardedMessages_other: String - public func ForwardedMessages(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._ForwardedMessages_zero, "\(value)") - case .one: - return String(format: self._ForwardedMessages_one, "\(value)") - case .two: - return String(format: self._ForwardedMessages_two, "\(value)") - case .few: - return String(format: self._ForwardedMessages_few, "\(value)") - case .many: - return String(format: self._ForwardedMessages_many, "\(value)") - case .other: - return String(format: self._ForwardedMessages_other, "\(value)") - } - } - private let _ForwardedVideos_zero: String - private let _ForwardedVideos_one: String - private let _ForwardedVideos_two: String - private let _ForwardedVideos_few: String - private let _ForwardedVideos_many: String - private let _ForwardedVideos_other: String - public func ForwardedVideos(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._ForwardedVideos_zero, "\(value)") - case .one: - return String(format: self._ForwardedVideos_one, "\(value)") - case .two: - return String(format: self._ForwardedVideos_two, "\(value)") - case .few: - return String(format: self._ForwardedVideos_few, "\(value)") - case .many: - return String(format: self._ForwardedVideos_many, "\(value)") - case .other: - return String(format: self._ForwardedVideos_other, "\(value)") - } - } - private let _MessageTimer_Minutes_zero: String - private let _MessageTimer_Minutes_one: String - private let _MessageTimer_Minutes_two: String - private let _MessageTimer_Minutes_few: String - private let _MessageTimer_Minutes_many: String - private let _MessageTimer_Minutes_other: String - public func MessageTimer_Minutes(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._MessageTimer_Minutes_zero, "\(value)") - case .one: - return String(format: self._MessageTimer_Minutes_one, "\(value)") - case .two: - return String(format: self._MessageTimer_Minutes_two, "\(value)") - case .few: - return String(format: self._MessageTimer_Minutes_few, "\(value)") - case .many: - return String(format: self._MessageTimer_Minutes_many, "\(value)") - case .other: - return String(format: self._MessageTimer_Minutes_other, "\(value)") - } - } - private let _Conversation_StatusOnline_zero: String - private let _Conversation_StatusOnline_one: String - private let _Conversation_StatusOnline_two: String - private let _Conversation_StatusOnline_few: String - private let _Conversation_StatusOnline_many: String - private let _Conversation_StatusOnline_other: String - public func Conversation_StatusOnline(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._Conversation_StatusOnline_zero, "\(value)") - case .one: - return String(format: self._Conversation_StatusOnline_one, "\(value)") - case .two: - return String(format: self._Conversation_StatusOnline_two, "\(value)") - case .few: - return String(format: self._Conversation_StatusOnline_few, "\(value)") - case .many: - return String(format: self._Conversation_StatusOnline_many, "\(value)") - case .other: - return String(format: self._Conversation_StatusOnline_other, "\(value)") - } - } - private let _MessageTimer_ShortMinutes_zero: String - private let _MessageTimer_ShortMinutes_one: String - private let _MessageTimer_ShortMinutes_two: String - private let _MessageTimer_ShortMinutes_few: String - private let _MessageTimer_ShortMinutes_many: String - private let _MessageTimer_ShortMinutes_other: String - public func MessageTimer_ShortMinutes(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._MessageTimer_ShortMinutes_zero, "\(value)") - case .one: - return String(format: self._MessageTimer_ShortMinutes_one, "\(value)") - case .two: - return String(format: self._MessageTimer_ShortMinutes_two, "\(value)") - case .few: - return String(format: self._MessageTimer_ShortMinutes_few, "\(value)") - case .many: - return String(format: self._MessageTimer_ShortMinutes_many, "\(value)") - case .other: - return String(format: self._MessageTimer_ShortMinutes_other, "\(value)") - } - } - private let _QuickSend_Photos_zero: String - private let _QuickSend_Photos_one: String - private let _QuickSend_Photos_two: String - private let _QuickSend_Photos_few: String - private let _QuickSend_Photos_many: String - private let _QuickSend_Photos_other: String - public func QuickSend_Photos(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._QuickSend_Photos_zero, "\(value)") - case .one: - return String(format: self._QuickSend_Photos_one, "\(value)") - case .two: - return String(format: self._QuickSend_Photos_two, "\(value)") - case .few: - return String(format: self._QuickSend_Photos_few, "\(value)") - case .many: - return String(format: self._QuickSend_Photos_many, "\(value)") - case .other: - return String(format: self._QuickSend_Photos_other, "\(value)") - } - } - private let _ServiceMessage_GameScoreExtended_zero: String - private let _ServiceMessage_GameScoreExtended_one: String - private let _ServiceMessage_GameScoreExtended_two: String - private let _ServiceMessage_GameScoreExtended_few: String - private let _ServiceMessage_GameScoreExtended_many: String - private let _ServiceMessage_GameScoreExtended_other: String - public func ServiceMessage_GameScoreExtended(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._ServiceMessage_GameScoreExtended_zero, "\(value)") - case .one: - return String(format: self._ServiceMessage_GameScoreExtended_one, "\(value)") - case .two: - return String(format: self._ServiceMessage_GameScoreExtended_two, "\(value)") - case .few: - return String(format: self._ServiceMessage_GameScoreExtended_few, "\(value)") - case .many: - return String(format: self._ServiceMessage_GameScoreExtended_many, "\(value)") - case .other: - return String(format: self._ServiceMessage_GameScoreExtended_other, "\(value)") - } - } - private let _SharedMedia_Generic_zero: String - private let _SharedMedia_Generic_one: String - private let _SharedMedia_Generic_two: String - private let _SharedMedia_Generic_few: String - private let _SharedMedia_Generic_many: String - private let _SharedMedia_Generic_other: String - public func SharedMedia_Generic(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._SharedMedia_Generic_zero, "\(value)") - case .one: - return String(format: self._SharedMedia_Generic_one, "\(value)") - case .two: - return String(format: self._SharedMedia_Generic_two, "\(value)") - case .few: - return String(format: self._SharedMedia_Generic_few, "\(value)") - case .many: - return String(format: self._SharedMedia_Generic_many, "\(value)") - case .other: - return String(format: self._SharedMedia_Generic_other, "\(value)") - } - } - private let _SharedMedia_Video_zero: String - private let _SharedMedia_Video_one: String - private let _SharedMedia_Video_two: String - private let _SharedMedia_Video_few: String - private let _SharedMedia_Video_many: String - private let _SharedMedia_Video_other: String - public func SharedMedia_Video(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._SharedMedia_Video_zero, "\(value)") - case .one: - return String(format: self._SharedMedia_Video_one, "\(value)") - case .two: - return String(format: self._SharedMedia_Video_two, "\(value)") - case .few: - return String(format: self._SharedMedia_Video_few, "\(value)") - case .many: - return String(format: self._SharedMedia_Video_many, "\(value)") - case .other: - return String(format: self._SharedMedia_Video_other, "\(value)") - } - } - private let _ServiceMessage_GameScoreSelfSimple_zero: String - private let _ServiceMessage_GameScoreSelfSimple_one: String - private let _ServiceMessage_GameScoreSelfSimple_two: String - private let _ServiceMessage_GameScoreSelfSimple_few: String - private let _ServiceMessage_GameScoreSelfSimple_many: String - private let _ServiceMessage_GameScoreSelfSimple_other: String - public func ServiceMessage_GameScoreSelfSimple(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._ServiceMessage_GameScoreSelfSimple_zero, "\(value)") - case .one: - return String(format: self._ServiceMessage_GameScoreSelfSimple_one, "\(value)") - case .two: - return String(format: self._ServiceMessage_GameScoreSelfSimple_two, "\(value)") - case .few: - return String(format: self._ServiceMessage_GameScoreSelfSimple_few, "\(value)") - case .many: - return String(format: self._ServiceMessage_GameScoreSelfSimple_many, "\(value)") - case .other: - return String(format: self._ServiceMessage_GameScoreSelfSimple_other, "\(value)") - } - } - private let _Media_ShareVideo_zero: String - private let _Media_ShareVideo_one: String - private let _Media_ShareVideo_two: String - private let _Media_ShareVideo_few: String - private let _Media_ShareVideo_many: String - private let _Media_ShareVideo_other: String - public func Media_ShareVideo(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._Media_ShareVideo_zero, "\(value)") - case .one: - return String(format: self._Media_ShareVideo_one, "\(value)") - case .two: - return String(format: self._Media_ShareVideo_two, "\(value)") - case .few: - return String(format: self._Media_ShareVideo_few, "\(value)") - case .many: - return String(format: self._Media_ShareVideo_many, "\(value)") - case .other: - return String(format: self._Media_ShareVideo_other, "\(value)") - } - } - private let _PrivacyLastSeenSettings_AddUsers_zero: String - private let _PrivacyLastSeenSettings_AddUsers_one: String - private let _PrivacyLastSeenSettings_AddUsers_two: String - private let _PrivacyLastSeenSettings_AddUsers_few: String - private let _PrivacyLastSeenSettings_AddUsers_many: String - private let _PrivacyLastSeenSettings_AddUsers_other: String - public func PrivacyLastSeenSettings_AddUsers(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._PrivacyLastSeenSettings_AddUsers_zero, "\(value)") - case .one: - return String(format: self._PrivacyLastSeenSettings_AddUsers_one, "\(value)") - case .two: - return String(format: self._PrivacyLastSeenSettings_AddUsers_two, "\(value)") - case .few: - return String(format: self._PrivacyLastSeenSettings_AddUsers_few, "\(value)") - case .many: - return String(format: self._PrivacyLastSeenSettings_AddUsers_many, "\(value)") - case .other: - return String(format: self._PrivacyLastSeenSettings_AddUsers_other, "\(value)") - } - } - private let _ForwardedContacts_zero: String - private let _ForwardedContacts_one: String - private let _ForwardedContacts_two: String - private let _ForwardedContacts_few: String - private let _ForwardedContacts_many: String - private let _ForwardedContacts_other: String - public func ForwardedContacts(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._ForwardedContacts_zero, "\(value)") - case .one: - return String(format: self._ForwardedContacts_one, "\(value)") - case .two: - return String(format: self._ForwardedContacts_two, "\(value)") - case .few: - return String(format: self._ForwardedContacts_few, "\(value)") - case .many: - return String(format: self._ForwardedContacts_many, "\(value)") - case .other: - return String(format: self._ForwardedContacts_other, "\(value)") - } - } - private let _Call_Seconds_zero: String - private let _Call_Seconds_one: String - private let _Call_Seconds_two: String - private let _Call_Seconds_few: String - private let _Call_Seconds_many: String - private let _Call_Seconds_other: String - public func Call_Seconds(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._Call_Seconds_zero, "\(value)") - case .one: - return String(format: self._Call_Seconds_one, "\(value)") - case .two: - return String(format: self._Call_Seconds_two, "\(value)") - case .few: - return String(format: self._Call_Seconds_few, "\(value)") - case .many: - return String(format: self._Call_Seconds_many, "\(value)") - case .other: - return String(format: self._Call_Seconds_other, "\(value)") - } - } - private let _ForwardedAuthorsOthers_zero: String - private let _ForwardedAuthorsOthers_one: String - private let _ForwardedAuthorsOthers_two: String - private let _ForwardedAuthorsOthers_few: String - private let _ForwardedAuthorsOthers_many: String - private let _ForwardedAuthorsOthers_other: String - public func ForwardedAuthorsOthers(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._ForwardedAuthorsOthers_zero, "\(value)") - case .one: - return String(format: self._ForwardedAuthorsOthers_one, "\(value)") - case .two: - return String(format: self._ForwardedAuthorsOthers_two, "\(value)") - case .few: - return String(format: self._ForwardedAuthorsOthers_few, "\(value)") - case .many: - return String(format: self._ForwardedAuthorsOthers_many, "\(value)") - case .other: - return String(format: self._ForwardedAuthorsOthers_other, "\(value)") - } - } - private let _DialogList_LiveLocationChatsCount_zero: String - private let _DialogList_LiveLocationChatsCount_one: String - private let _DialogList_LiveLocationChatsCount_two: String - private let _DialogList_LiveLocationChatsCount_few: String - private let _DialogList_LiveLocationChatsCount_many: String - private let _DialogList_LiveLocationChatsCount_other: String - public func DialogList_LiveLocationChatsCount(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._DialogList_LiveLocationChatsCount_zero, "\(value)") - case .one: - return String(format: self._DialogList_LiveLocationChatsCount_one, "\(value)") - case .two: - return String(format: self._DialogList_LiveLocationChatsCount_two, "\(value)") - case .few: - return String(format: self._DialogList_LiveLocationChatsCount_few, "\(value)") - case .many: - return String(format: self._DialogList_LiveLocationChatsCount_many, "\(value)") - case .other: - return String(format: self._DialogList_LiveLocationChatsCount_other, "\(value)") - } - } - private let _MessageTimer_Years_zero: String - private let _MessageTimer_Years_one: String - private let _MessageTimer_Years_two: String - private let _MessageTimer_Years_few: String - private let _MessageTimer_Years_many: String - private let _MessageTimer_Years_other: String - public func MessageTimer_Years(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._MessageTimer_Years_zero, "\(value)") - case .one: - return String(format: self._MessageTimer_Years_one, "\(value)") - case .two: - return String(format: self._MessageTimer_Years_two, "\(value)") - case .few: - return String(format: self._MessageTimer_Years_few, "\(value)") - case .many: - return String(format: self._MessageTimer_Years_many, "\(value)") - case .other: - return String(format: self._MessageTimer_Years_other, "\(value)") - } - } - private let _LiveLocationUpdated_MinutesAgo_zero: String - private let _LiveLocationUpdated_MinutesAgo_one: String - private let _LiveLocationUpdated_MinutesAgo_two: String - private let _LiveLocationUpdated_MinutesAgo_few: String - private let _LiveLocationUpdated_MinutesAgo_many: String - private let _LiveLocationUpdated_MinutesAgo_other: String - public func LiveLocationUpdated_MinutesAgo(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._LiveLocationUpdated_MinutesAgo_zero, "\(value)") - case .one: - return String(format: self._LiveLocationUpdated_MinutesAgo_one, "\(value)") - case .two: - return String(format: self._LiveLocationUpdated_MinutesAgo_two, "\(value)") - case .few: - return String(format: self._LiveLocationUpdated_MinutesAgo_few, "\(value)") - case .many: - return String(format: self._LiveLocationUpdated_MinutesAgo_many, "\(value)") - case .other: - return String(format: self._LiveLocationUpdated_MinutesAgo_other, "\(value)") - } - } - private let _StickerPack_RemoveMaskCount_zero: String - private let _StickerPack_RemoveMaskCount_one: String - private let _StickerPack_RemoveMaskCount_two: String - private let _StickerPack_RemoveMaskCount_few: String - private let _StickerPack_RemoveMaskCount_many: String - private let _StickerPack_RemoveMaskCount_other: String - public func StickerPack_RemoveMaskCount(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._StickerPack_RemoveMaskCount_zero, "\(value)") - case .one: - return String(format: self._StickerPack_RemoveMaskCount_one, "\(value)") - case .two: - return String(format: self._StickerPack_RemoveMaskCount_two, "\(value)") - case .few: - return String(format: self._StickerPack_RemoveMaskCount_few, "\(value)") - case .many: - return String(format: self._StickerPack_RemoveMaskCount_many, "\(value)") - case .other: - return String(format: self._StickerPack_RemoveMaskCount_other, "\(value)") - } - } - private let _ForwardedVideoMessages_zero: String - private let _ForwardedVideoMessages_one: String - private let _ForwardedVideoMessages_two: String - private let _ForwardedVideoMessages_few: String - private let _ForwardedVideoMessages_many: String - private let _ForwardedVideoMessages_other: String - public func ForwardedVideoMessages(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._ForwardedVideoMessages_zero, "\(value)") - case .one: - return String(format: self._ForwardedVideoMessages_one, "\(value)") - case .two: - return String(format: self._ForwardedVideoMessages_two, "\(value)") - case .few: - return String(format: self._ForwardedVideoMessages_few, "\(value)") - case .many: - return String(format: self._ForwardedVideoMessages_many, "\(value)") - case .other: - return String(format: self._ForwardedVideoMessages_other, "\(value)") - } - } - private let _MessageTimer_ShortHours_zero: String - private let _MessageTimer_ShortHours_one: String - private let _MessageTimer_ShortHours_two: String - private let _MessageTimer_ShortHours_few: String - private let _MessageTimer_ShortHours_many: String - private let _MessageTimer_ShortHours_other: String - public func MessageTimer_ShortHours(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._MessageTimer_ShortHours_zero, "\(value)") - case .one: - return String(format: self._MessageTimer_ShortHours_one, "\(value)") - case .two: - return String(format: self._MessageTimer_ShortHours_two, "\(value)") - case .few: - return String(format: self._MessageTimer_ShortHours_few, "\(value)") - case .many: - return String(format: self._MessageTimer_ShortHours_many, "\(value)") - case .other: - return String(format: self._MessageTimer_ShortHours_other, "\(value)") - } - } - private let _Watch_LastSeen_HoursAgo_zero: String - private let _Watch_LastSeen_HoursAgo_one: String - private let _Watch_LastSeen_HoursAgo_two: String - private let _Watch_LastSeen_HoursAgo_few: String - private let _Watch_LastSeen_HoursAgo_many: String - private let _Watch_LastSeen_HoursAgo_other: String - public func Watch_LastSeen_HoursAgo(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._Watch_LastSeen_HoursAgo_zero, "\(value)") - case .one: - return String(format: self._Watch_LastSeen_HoursAgo_one, "\(value)") - case .two: - return String(format: self._Watch_LastSeen_HoursAgo_two, "\(value)") - case .few: - return String(format: self._Watch_LastSeen_HoursAgo_few, "\(value)") - case .many: - return String(format: self._Watch_LastSeen_HoursAgo_many, "\(value)") - case .other: - return String(format: self._Watch_LastSeen_HoursAgo_other, "\(value)") - } - } - private let _AttachmentMenu_SendVideo_zero: String - private let _AttachmentMenu_SendVideo_one: String - private let _AttachmentMenu_SendVideo_two: String - private let _AttachmentMenu_SendVideo_few: String - private let _AttachmentMenu_SendVideo_many: String - private let _AttachmentMenu_SendVideo_other: String - public func AttachmentMenu_SendVideo(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._AttachmentMenu_SendVideo_zero, "\(value)") - case .one: - return String(format: self._AttachmentMenu_SendVideo_one, "\(value)") - case .two: - return String(format: self._AttachmentMenu_SendVideo_two, "\(value)") - case .few: - return String(format: self._AttachmentMenu_SendVideo_few, "\(value)") - case .many: - return String(format: self._AttachmentMenu_SendVideo_many, "\(value)") - case .other: - return String(format: self._AttachmentMenu_SendVideo_other, "\(value)") + return String(format: self._GroupInfo_ParticipantCount_other, "\(value)") } } private let _MessageTimer_Weeks_zero: String @@ -5307,48 +3702,136 @@ public final class PresentationStrings { return String(format: self._MessageTimer_Weeks_other, "\(value)") } } - private let _StickerPack_StickerCount_zero: String - private let _StickerPack_StickerCount_one: String - private let _StickerPack_StickerCount_two: String - private let _StickerPack_StickerCount_few: String - private let _StickerPack_StickerCount_many: String - private let _StickerPack_StickerCount_other: String - public func StickerPack_StickerCount(_ value: Int32) -> String { + private let _ForwardedAudios_zero: String + private let _ForwardedAudios_one: String + private let _ForwardedAudios_two: String + private let _ForwardedAudios_few: String + private let _ForwardedAudios_many: String + private let _ForwardedAudios_other: String + public func ForwardedAudios(_ value: Int32) -> String { switch presentationStringsPluralizationForm(self.lc, value) { case .zero: - return String(format: self._StickerPack_StickerCount_zero, "\(value)") + return String(format: self._ForwardedAudios_zero, "\(value)") case .one: - return String(format: self._StickerPack_StickerCount_one, "\(value)") + return String(format: self._ForwardedAudios_one, "\(value)") case .two: - return String(format: self._StickerPack_StickerCount_two, "\(value)") + return String(format: self._ForwardedAudios_two, "\(value)") case .few: - return String(format: self._StickerPack_StickerCount_few, "\(value)") + return String(format: self._ForwardedAudios_few, "\(value)") case .many: - return String(format: self._StickerPack_StickerCount_many, "\(value)") + return String(format: self._ForwardedAudios_many, "\(value)") case .other: - return String(format: self._StickerPack_StickerCount_other, "\(value)") + return String(format: self._ForwardedAudios_other, "\(value)") } } - private let _ServiceMessage_GameScoreSimple_zero: String - private let _ServiceMessage_GameScoreSimple_one: String - private let _ServiceMessage_GameScoreSimple_two: String - private let _ServiceMessage_GameScoreSimple_few: String - private let _ServiceMessage_GameScoreSimple_many: String - private let _ServiceMessage_GameScoreSimple_other: String - public func ServiceMessage_GameScoreSimple(_ value: Int32) -> String { + private let _Notification_GameScoreSimple_zero: String + private let _Notification_GameScoreSimple_one: String + private let _Notification_GameScoreSimple_two: String + private let _Notification_GameScoreSimple_few: String + private let _Notification_GameScoreSimple_many: String + private let _Notification_GameScoreSimple_other: String + public func Notification_GameScoreSimple(_ value: Int32) -> String { switch presentationStringsPluralizationForm(self.lc, value) { case .zero: - return String(format: self._ServiceMessage_GameScoreSimple_zero, "\(value)") + return String(format: self._Notification_GameScoreSimple_zero, "\(value)") case .one: - return String(format: self._ServiceMessage_GameScoreSimple_one, "\(value)") + return String(format: self._Notification_GameScoreSimple_one, "\(value)") case .two: - return String(format: self._ServiceMessage_GameScoreSimple_two, "\(value)") + return String(format: self._Notification_GameScoreSimple_two, "\(value)") case .few: - return String(format: self._ServiceMessage_GameScoreSimple_few, "\(value)") + return String(format: self._Notification_GameScoreSimple_few, "\(value)") case .many: - return String(format: self._ServiceMessage_GameScoreSimple_many, "\(value)") + return String(format: self._Notification_GameScoreSimple_many, "\(value)") case .other: - return String(format: self._ServiceMessage_GameScoreSimple_other, "\(value)") + return String(format: self._Notification_GameScoreSimple_other, "\(value)") + } + } + private let _AttachmentMenu_SendGif_zero: String + private let _AttachmentMenu_SendGif_one: String + private let _AttachmentMenu_SendGif_two: String + private let _AttachmentMenu_SendGif_few: String + private let _AttachmentMenu_SendGif_many: String + private let _AttachmentMenu_SendGif_other: String + public func AttachmentMenu_SendGif(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._AttachmentMenu_SendGif_zero, "\(value)") + case .one: + return String(format: self._AttachmentMenu_SendGif_one, "\(value)") + case .two: + return String(format: self._AttachmentMenu_SendGif_two, "\(value)") + case .few: + return String(format: self._AttachmentMenu_SendGif_few, "\(value)") + case .many: + return String(format: self._AttachmentMenu_SendGif_many, "\(value)") + case .other: + return String(format: self._AttachmentMenu_SendGif_other, "\(value)") + } + } + private let _Invitation_Members_zero: String + private let _Invitation_Members_one: String + private let _Invitation_Members_two: String + private let _Invitation_Members_few: String + private let _Invitation_Members_many: String + private let _Invitation_Members_other: String + public func Invitation_Members(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._Invitation_Members_zero, "\(value)") + case .one: + return String(format: self._Invitation_Members_one, "\(value)") + case .two: + return String(format: self._Invitation_Members_two, "\(value)") + case .few: + return String(format: self._Invitation_Members_few, "\(value)") + case .many: + return String(format: self._Invitation_Members_many, "\(value)") + case .other: + return String(format: self._Invitation_Members_other, "\(value)") + } + } + private let _AttachmentMenu_SendItem_zero: String + private let _AttachmentMenu_SendItem_one: String + private let _AttachmentMenu_SendItem_two: String + private let _AttachmentMenu_SendItem_few: String + private let _AttachmentMenu_SendItem_many: String + private let _AttachmentMenu_SendItem_other: String + public func AttachmentMenu_SendItem(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._AttachmentMenu_SendItem_zero, "\(value)") + case .one: + return String(format: self._AttachmentMenu_SendItem_one, "\(value)") + case .two: + return String(format: self._AttachmentMenu_SendItem_two, "\(value)") + case .few: + return String(format: self._AttachmentMenu_SendItem_few, "\(value)") + case .many: + return String(format: self._AttachmentMenu_SendItem_many, "\(value)") + case .other: + return String(format: self._AttachmentMenu_SendItem_other, "\(value)") + } + } + private let _MessageTimer_ShortHours_zero: String + private let _MessageTimer_ShortHours_one: String + private let _MessageTimer_ShortHours_two: String + private let _MessageTimer_ShortHours_few: String + private let _MessageTimer_ShortHours_many: String + private let _MessageTimer_ShortHours_other: String + public func MessageTimer_ShortHours(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._MessageTimer_ShortHours_zero, "\(value)") + case .one: + return String(format: self._MessageTimer_ShortHours_one, "\(value)") + case .two: + return String(format: self._MessageTimer_ShortHours_two, "\(value)") + case .few: + return String(format: self._MessageTimer_ShortHours_few, "\(value)") + case .many: + return String(format: self._MessageTimer_ShortHours_many, "\(value)") + case .other: + return String(format: self._MessageTimer_ShortHours_other, "\(value)") } } private let _ForwardedFiles_zero: String @@ -5373,26 +3856,532 @@ public final class PresentationStrings { return String(format: self._ForwardedFiles_other, "\(value)") } } - private let _GroupInfo_ParticipantCount_zero: String - private let _GroupInfo_ParticipantCount_one: String - private let _GroupInfo_ParticipantCount_two: String - private let _GroupInfo_ParticipantCount_few: String - private let _GroupInfo_ParticipantCount_many: String - private let _GroupInfo_ParticipantCount_other: String - public func GroupInfo_ParticipantCount(_ value: Int32) -> String { + private let _MuteExpires_Minutes_zero: String + private let _MuteExpires_Minutes_one: String + private let _MuteExpires_Minutes_two: String + private let _MuteExpires_Minutes_few: String + private let _MuteExpires_Minutes_many: String + private let _MuteExpires_Minutes_other: String + public func MuteExpires_Minutes(_ value: Int32) -> String { switch presentationStringsPluralizationForm(self.lc, value) { case .zero: - return String(format: self._GroupInfo_ParticipantCount_zero, "\(value)") + return String(format: self._MuteExpires_Minutes_zero, "\(value)") case .one: - return String(format: self._GroupInfo_ParticipantCount_one, "\(value)") + return String(format: self._MuteExpires_Minutes_one, "\(value)") case .two: - return String(format: self._GroupInfo_ParticipantCount_two, "\(value)") + return String(format: self._MuteExpires_Minutes_two, "\(value)") case .few: - return String(format: self._GroupInfo_ParticipantCount_few, "\(value)") + return String(format: self._MuteExpires_Minutes_few, "\(value)") case .many: - return String(format: self._GroupInfo_ParticipantCount_many, "\(value)") + return String(format: self._MuteExpires_Minutes_many, "\(value)") case .other: - return String(format: self._GroupInfo_ParticipantCount_other, "\(value)") + return String(format: self._MuteExpires_Minutes_other, "\(value)") + } + } + private let _ForwardedAuthorsOthers_zero: String + private let _ForwardedAuthorsOthers_one: String + private let _ForwardedAuthorsOthers_two: String + private let _ForwardedAuthorsOthers_few: String + private let _ForwardedAuthorsOthers_many: String + private let _ForwardedAuthorsOthers_other: String + public func ForwardedAuthorsOthers(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._ForwardedAuthorsOthers_zero, "\(value)") + case .one: + return String(format: self._ForwardedAuthorsOthers_one, "\(value)") + case .two: + return String(format: self._ForwardedAuthorsOthers_two, "\(value)") + case .few: + return String(format: self._ForwardedAuthorsOthers_few, "\(value)") + case .many: + return String(format: self._ForwardedAuthorsOthers_many, "\(value)") + case .other: + return String(format: self._ForwardedAuthorsOthers_other, "\(value)") + } + } + private let _Map_ETAHours_zero: String + private let _Map_ETAHours_one: String + private let _Map_ETAHours_two: String + private let _Map_ETAHours_few: String + private let _Map_ETAHours_many: String + private let _Map_ETAHours_other: String + public func Map_ETAHours(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._Map_ETAHours_zero, "\(value)") + case .one: + return String(format: self._Map_ETAHours_one, "\(value)") + case .two: + return String(format: self._Map_ETAHours_two, "\(value)") + case .few: + return String(format: self._Map_ETAHours_few, "\(value)") + case .many: + return String(format: self._Map_ETAHours_many, "\(value)") + case .other: + return String(format: self._Map_ETAHours_other, "\(value)") + } + } + private let _ForwardedMessages_zero: String + private let _ForwardedMessages_one: String + private let _ForwardedMessages_two: String + private let _ForwardedMessages_few: String + private let _ForwardedMessages_many: String + private let _ForwardedMessages_other: String + public func ForwardedMessages(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._ForwardedMessages_zero, "\(value)") + case .one: + return String(format: self._ForwardedMessages_one, "\(value)") + case .two: + return String(format: self._ForwardedMessages_two, "\(value)") + case .few: + return String(format: self._ForwardedMessages_few, "\(value)") + case .many: + return String(format: self._ForwardedMessages_many, "\(value)") + case .other: + return String(format: self._ForwardedMessages_other, "\(value)") + } + } + private let _Notification_GameScoreSelfSimple_zero: String + private let _Notification_GameScoreSelfSimple_one: String + private let _Notification_GameScoreSelfSimple_two: String + private let _Notification_GameScoreSelfSimple_few: String + private let _Notification_GameScoreSelfSimple_many: String + private let _Notification_GameScoreSelfSimple_other: String + public func Notification_GameScoreSelfSimple(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._Notification_GameScoreSelfSimple_zero, "\(value)") + case .one: + return String(format: self._Notification_GameScoreSelfSimple_one, "\(value)") + case .two: + return String(format: self._Notification_GameScoreSelfSimple_two, "\(value)") + case .few: + return String(format: self._Notification_GameScoreSelfSimple_few, "\(value)") + case .many: + return String(format: self._Notification_GameScoreSelfSimple_many, "\(value)") + case .other: + return String(format: self._Notification_GameScoreSelfSimple_other, "\(value)") + } + } + private let _Contacts_ImportersCount_zero: String + private let _Contacts_ImportersCount_one: String + private let _Contacts_ImportersCount_two: String + private let _Contacts_ImportersCount_few: String + private let _Contacts_ImportersCount_many: String + private let _Contacts_ImportersCount_other: String + public func Contacts_ImportersCount(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._Contacts_ImportersCount_zero, "\(value)") + case .one: + return String(format: self._Contacts_ImportersCount_one, "\(value)") + case .two: + return String(format: self._Contacts_ImportersCount_two, "\(value)") + case .few: + return String(format: self._Contacts_ImportersCount_few, "\(value)") + case .many: + return String(format: self._Contacts_ImportersCount_many, "\(value)") + case .other: + return String(format: self._Contacts_ImportersCount_other, "\(value)") + } + } + private let _LastSeen_HoursAgo_zero: String + private let _LastSeen_HoursAgo_one: String + private let _LastSeen_HoursAgo_two: String + private let _LastSeen_HoursAgo_few: String + private let _LastSeen_HoursAgo_many: String + private let _LastSeen_HoursAgo_other: String + public func LastSeen_HoursAgo(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._LastSeen_HoursAgo_zero, "\(value)") + case .one: + return String(format: self._LastSeen_HoursAgo_one, "\(value)") + case .two: + return String(format: self._LastSeen_HoursAgo_two, "\(value)") + case .few: + return String(format: self._LastSeen_HoursAgo_few, "\(value)") + case .many: + return String(format: self._LastSeen_HoursAgo_many, "\(value)") + case .other: + return String(format: self._LastSeen_HoursAgo_other, "\(value)") + } + } + private let _AttachmentMenu_SendPhoto_zero: String + private let _AttachmentMenu_SendPhoto_one: String + private let _AttachmentMenu_SendPhoto_two: String + private let _AttachmentMenu_SendPhoto_few: String + private let _AttachmentMenu_SendPhoto_many: String + private let _AttachmentMenu_SendPhoto_other: String + public func AttachmentMenu_SendPhoto(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._AttachmentMenu_SendPhoto_zero, "\(value)") + case .one: + return String(format: self._AttachmentMenu_SendPhoto_one, "\(value)") + case .two: + return String(format: self._AttachmentMenu_SendPhoto_two, "\(value)") + case .few: + return String(format: self._AttachmentMenu_SendPhoto_few, "\(value)") + case .many: + return String(format: self._AttachmentMenu_SendPhoto_many, "\(value)") + case .other: + return String(format: self._AttachmentMenu_SendPhoto_other, "\(value)") + } + } + private let _MuteFor_Hours_zero: String + private let _MuteFor_Hours_one: String + private let _MuteFor_Hours_two: String + private let _MuteFor_Hours_few: String + private let _MuteFor_Hours_many: String + private let _MuteFor_Hours_other: String + public func MuteFor_Hours(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._MuteFor_Hours_zero, "\(value)") + case .one: + return String(format: self._MuteFor_Hours_one, "\(value)") + case .two: + return String(format: self._MuteFor_Hours_two, "\(value)") + case .few: + return String(format: self._MuteFor_Hours_few, "\(value)") + case .many: + return String(format: self._MuteFor_Hours_many, "\(value)") + case .other: + return String(format: self._MuteFor_Hours_other, "\(value)") + } + } + private let _AttachmentMenu_SendVideo_zero: String + private let _AttachmentMenu_SendVideo_one: String + private let _AttachmentMenu_SendVideo_two: String + private let _AttachmentMenu_SendVideo_few: String + private let _AttachmentMenu_SendVideo_many: String + private let _AttachmentMenu_SendVideo_other: String + public func AttachmentMenu_SendVideo(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._AttachmentMenu_SendVideo_zero, "\(value)") + case .one: + return String(format: self._AttachmentMenu_SendVideo_one, "\(value)") + case .two: + return String(format: self._AttachmentMenu_SendVideo_two, "\(value)") + case .few: + return String(format: self._AttachmentMenu_SendVideo_few, "\(value)") + case .many: + return String(format: self._AttachmentMenu_SendVideo_many, "\(value)") + case .other: + return String(format: self._AttachmentMenu_SendVideo_other, "\(value)") + } + } + private let _SharedMedia_Photo_zero: String + private let _SharedMedia_Photo_one: String + private let _SharedMedia_Photo_two: String + private let _SharedMedia_Photo_few: String + private let _SharedMedia_Photo_many: String + private let _SharedMedia_Photo_other: String + public func SharedMedia_Photo(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._SharedMedia_Photo_zero, "\(value)") + case .one: + return String(format: self._SharedMedia_Photo_one, "\(value)") + case .two: + return String(format: self._SharedMedia_Photo_two, "\(value)") + case .few: + return String(format: self._SharedMedia_Photo_few, "\(value)") + case .many: + return String(format: self._SharedMedia_Photo_many, "\(value)") + case .other: + return String(format: self._SharedMedia_Photo_other, "\(value)") + } + } + private let _ForwardedGifs_zero: String + private let _ForwardedGifs_one: String + private let _ForwardedGifs_two: String + private let _ForwardedGifs_few: String + private let _ForwardedGifs_many: String + private let _ForwardedGifs_other: String + public func ForwardedGifs(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._ForwardedGifs_zero, "\(value)") + case .one: + return String(format: self._ForwardedGifs_one, "\(value)") + case .two: + return String(format: self._ForwardedGifs_two, "\(value)") + case .few: + return String(format: self._ForwardedGifs_few, "\(value)") + case .many: + return String(format: self._ForwardedGifs_many, "\(value)") + case .other: + return String(format: self._ForwardedGifs_other, "\(value)") + } + } + private let _ServiceMessage_GameScoreExtended_zero: String + private let _ServiceMessage_GameScoreExtended_one: String + private let _ServiceMessage_GameScoreExtended_two: String + private let _ServiceMessage_GameScoreExtended_few: String + private let _ServiceMessage_GameScoreExtended_many: String + private let _ServiceMessage_GameScoreExtended_other: String + public func ServiceMessage_GameScoreExtended(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._ServiceMessage_GameScoreExtended_zero, "\(value)") + case .one: + return String(format: self._ServiceMessage_GameScoreExtended_one, "\(value)") + case .two: + return String(format: self._ServiceMessage_GameScoreExtended_two, "\(value)") + case .few: + return String(format: self._ServiceMessage_GameScoreExtended_few, "\(value)") + case .many: + return String(format: self._ServiceMessage_GameScoreExtended_many, "\(value)") + case .other: + return String(format: self._ServiceMessage_GameScoreExtended_other, "\(value)") + } + } + private let _StickerPack_RemoveStickerCount_zero: String + private let _StickerPack_RemoveStickerCount_one: String + private let _StickerPack_RemoveStickerCount_two: String + private let _StickerPack_RemoveStickerCount_few: String + private let _StickerPack_RemoveStickerCount_many: String + private let _StickerPack_RemoveStickerCount_other: String + public func StickerPack_RemoveStickerCount(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._StickerPack_RemoveStickerCount_zero, "\(value)") + case .one: + return String(format: self._StickerPack_RemoveStickerCount_one, "\(value)") + case .two: + return String(format: self._StickerPack_RemoveStickerCount_two, "\(value)") + case .few: + return String(format: self._StickerPack_RemoveStickerCount_few, "\(value)") + case .many: + return String(format: self._StickerPack_RemoveStickerCount_many, "\(value)") + case .other: + return String(format: self._StickerPack_RemoveStickerCount_other, "\(value)") + } + } + private let _Media_ShareItem_zero: String + private let _Media_ShareItem_one: String + private let _Media_ShareItem_two: String + private let _Media_ShareItem_few: String + private let _Media_ShareItem_many: String + private let _Media_ShareItem_other: String + public func Media_ShareItem(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._Media_ShareItem_zero, "\(value)") + case .one: + return String(format: self._Media_ShareItem_one, "\(value)") + case .two: + return String(format: self._Media_ShareItem_two, "\(value)") + case .few: + return String(format: self._Media_ShareItem_few, "\(value)") + case .many: + return String(format: self._Media_ShareItem_many, "\(value)") + case .other: + return String(format: self._Media_ShareItem_other, "\(value)") + } + } + private let _MuteFor_Days_zero: String + private let _MuteFor_Days_one: String + private let _MuteFor_Days_two: String + private let _MuteFor_Days_few: String + private let _MuteFor_Days_many: String + private let _MuteFor_Days_other: String + public func MuteFor_Days(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._MuteFor_Days_zero, "\(value)") + case .one: + return String(format: self._MuteFor_Days_one, "\(value)") + case .two: + return String(format: self._MuteFor_Days_two, "\(value)") + case .few: + return String(format: self._MuteFor_Days_few, "\(value)") + case .many: + return String(format: self._MuteFor_Days_many, "\(value)") + case .other: + return String(format: self._MuteFor_Days_other, "\(value)") + } + } + private let _ForwardedContacts_zero: String + private let _ForwardedContacts_one: String + private let _ForwardedContacts_two: String + private let _ForwardedContacts_few: String + private let _ForwardedContacts_many: String + private let _ForwardedContacts_other: String + public func ForwardedContacts(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._ForwardedContacts_zero, "\(value)") + case .one: + return String(format: self._ForwardedContacts_one, "\(value)") + case .two: + return String(format: self._ForwardedContacts_two, "\(value)") + case .few: + return String(format: self._ForwardedContacts_few, "\(value)") + case .many: + return String(format: self._ForwardedContacts_many, "\(value)") + case .other: + return String(format: self._ForwardedContacts_other, "\(value)") + } + } + private let _Notification_GameScoreExtended_zero: String + private let _Notification_GameScoreExtended_one: String + private let _Notification_GameScoreExtended_two: String + private let _Notification_GameScoreExtended_few: String + private let _Notification_GameScoreExtended_many: String + private let _Notification_GameScoreExtended_other: String + public func Notification_GameScoreExtended(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._Notification_GameScoreExtended_zero, "\(value)") + case .one: + return String(format: self._Notification_GameScoreExtended_one, "\(value)") + case .two: + return String(format: self._Notification_GameScoreExtended_two, "\(value)") + case .few: + return String(format: self._Notification_GameScoreExtended_few, "\(value)") + case .many: + return String(format: self._Notification_GameScoreExtended_many, "\(value)") + case .other: + return String(format: self._Notification_GameScoreExtended_other, "\(value)") + } + } + private let _Call_Seconds_zero: String + private let _Call_Seconds_one: String + private let _Call_Seconds_two: String + private let _Call_Seconds_few: String + private let _Call_Seconds_many: String + private let _Call_Seconds_other: String + public func Call_Seconds(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._Call_Seconds_zero, "\(value)") + case .one: + return String(format: self._Call_Seconds_one, "\(value)") + case .two: + return String(format: self._Call_Seconds_two, "\(value)") + case .few: + return String(format: self._Call_Seconds_few, "\(value)") + case .many: + return String(format: self._Call_Seconds_many, "\(value)") + case .other: + return String(format: self._Call_Seconds_other, "\(value)") + } + } + private let _ForwardedStickers_zero: String + private let _ForwardedStickers_one: String + private let _ForwardedStickers_two: String + private let _ForwardedStickers_few: String + private let _ForwardedStickers_many: String + private let _ForwardedStickers_other: String + public func ForwardedStickers(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._ForwardedStickers_zero, "\(value)") + case .one: + return String(format: self._ForwardedStickers_one, "\(value)") + case .two: + return String(format: self._ForwardedStickers_two, "\(value)") + case .few: + return String(format: self._ForwardedStickers_few, "\(value)") + case .many: + return String(format: self._ForwardedStickers_many, "\(value)") + case .other: + return String(format: self._ForwardedStickers_other, "\(value)") + } + } + private let _StickerPack_RemoveMaskCount_zero: String + private let _StickerPack_RemoveMaskCount_one: String + private let _StickerPack_RemoveMaskCount_two: String + private let _StickerPack_RemoveMaskCount_few: String + private let _StickerPack_RemoveMaskCount_many: String + private let _StickerPack_RemoveMaskCount_other: String + public func StickerPack_RemoveMaskCount(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._StickerPack_RemoveMaskCount_zero, "\(value)") + case .one: + return String(format: self._StickerPack_RemoveMaskCount_one, "\(value)") + case .two: + return String(format: self._StickerPack_RemoveMaskCount_two, "\(value)") + case .few: + return String(format: self._StickerPack_RemoveMaskCount_few, "\(value)") + case .many: + return String(format: self._StickerPack_RemoveMaskCount_many, "\(value)") + case .other: + return String(format: self._StickerPack_RemoveMaskCount_other, "\(value)") + } + } + private let _Watch_LastSeen_MinutesAgo_zero: String + private let _Watch_LastSeen_MinutesAgo_one: String + private let _Watch_LastSeen_MinutesAgo_two: String + private let _Watch_LastSeen_MinutesAgo_few: String + private let _Watch_LastSeen_MinutesAgo_many: String + private let _Watch_LastSeen_MinutesAgo_other: String + public func Watch_LastSeen_MinutesAgo(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._Watch_LastSeen_MinutesAgo_zero, "\(value)") + case .one: + return String(format: self._Watch_LastSeen_MinutesAgo_one, "\(value)") + case .two: + return String(format: self._Watch_LastSeen_MinutesAgo_two, "\(value)") + case .few: + return String(format: self._Watch_LastSeen_MinutesAgo_few, "\(value)") + case .many: + return String(format: self._Watch_LastSeen_MinutesAgo_many, "\(value)") + case .other: + return String(format: self._Watch_LastSeen_MinutesAgo_other, "\(value)") + } + } + private let _LiveLocation_MenuChatsCount_zero: String + private let _LiveLocation_MenuChatsCount_one: String + private let _LiveLocation_MenuChatsCount_two: String + private let _LiveLocation_MenuChatsCount_few: String + private let _LiveLocation_MenuChatsCount_many: String + private let _LiveLocation_MenuChatsCount_other: String + public func LiveLocation_MenuChatsCount(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._LiveLocation_MenuChatsCount_zero, "\(value)") + case .one: + return String(format: self._LiveLocation_MenuChatsCount_one, "\(value)") + case .two: + return String(format: self._LiveLocation_MenuChatsCount_two, "\(value)") + case .few: + return String(format: self._LiveLocation_MenuChatsCount_few, "\(value)") + case .many: + return String(format: self._LiveLocation_MenuChatsCount_many, "\(value)") + case .other: + return String(format: self._LiveLocation_MenuChatsCount_other, "\(value)") + } + } + private let _Notifications_Exceptions_zero: String + private let _Notifications_Exceptions_one: String + private let _Notifications_Exceptions_two: String + private let _Notifications_Exceptions_few: String + private let _Notifications_Exceptions_many: String + private let _Notifications_Exceptions_other: String + public func Notifications_Exceptions(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._Notifications_Exceptions_zero, "\(value)") + case .one: + return String(format: self._Notifications_Exceptions_one, "\(value)") + case .two: + return String(format: self._Notifications_Exceptions_two, "\(value)") + case .few: + return String(format: self._Notifications_Exceptions_few, "\(value)") + case .many: + return String(format: self._Notifications_Exceptions_many, "\(value)") + case .other: + return String(format: self._Notifications_Exceptions_other, "\(value)") } } private let _ForwardedLocations_zero: String @@ -5417,6 +4406,336 @@ public final class PresentationStrings { return String(format: self._ForwardedLocations_other, "\(value)") } } + private let _ServiceMessage_GameScoreSelfExtended_zero: String + private let _ServiceMessage_GameScoreSelfExtended_one: String + private let _ServiceMessage_GameScoreSelfExtended_two: String + private let _ServiceMessage_GameScoreSelfExtended_few: String + private let _ServiceMessage_GameScoreSelfExtended_many: String + private let _ServiceMessage_GameScoreSelfExtended_other: String + public func ServiceMessage_GameScoreSelfExtended(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._ServiceMessage_GameScoreSelfExtended_zero, "\(value)") + case .one: + return String(format: self._ServiceMessage_GameScoreSelfExtended_one, "\(value)") + case .two: + return String(format: self._ServiceMessage_GameScoreSelfExtended_two, "\(value)") + case .few: + return String(format: self._ServiceMessage_GameScoreSelfExtended_few, "\(value)") + case .many: + return String(format: self._ServiceMessage_GameScoreSelfExtended_many, "\(value)") + case .other: + return String(format: self._ServiceMessage_GameScoreSelfExtended_other, "\(value)") + } + } + private let _MessageTimer_Minutes_zero: String + private let _MessageTimer_Minutes_one: String + private let _MessageTimer_Minutes_two: String + private let _MessageTimer_Minutes_few: String + private let _MessageTimer_Minutes_many: String + private let _MessageTimer_Minutes_other: String + public func MessageTimer_Minutes(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._MessageTimer_Minutes_zero, "\(value)") + case .one: + return String(format: self._MessageTimer_Minutes_one, "\(value)") + case .two: + return String(format: self._MessageTimer_Minutes_two, "\(value)") + case .few: + return String(format: self._MessageTimer_Minutes_few, "\(value)") + case .many: + return String(format: self._MessageTimer_Minutes_many, "\(value)") + case .other: + return String(format: self._MessageTimer_Minutes_other, "\(value)") + } + } + private let _ServiceMessage_GameScoreSelfSimple_zero: String + private let _ServiceMessage_GameScoreSelfSimple_one: String + private let _ServiceMessage_GameScoreSelfSimple_two: String + private let _ServiceMessage_GameScoreSelfSimple_few: String + private let _ServiceMessage_GameScoreSelfSimple_many: String + private let _ServiceMessage_GameScoreSelfSimple_other: String + public func ServiceMessage_GameScoreSelfSimple(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._ServiceMessage_GameScoreSelfSimple_zero, "\(value)") + case .one: + return String(format: self._ServiceMessage_GameScoreSelfSimple_one, "\(value)") + case .two: + return String(format: self._ServiceMessage_GameScoreSelfSimple_two, "\(value)") + case .few: + return String(format: self._ServiceMessage_GameScoreSelfSimple_few, "\(value)") + case .many: + return String(format: self._ServiceMessage_GameScoreSelfSimple_many, "\(value)") + case .other: + return String(format: self._ServiceMessage_GameScoreSelfSimple_other, "\(value)") + } + } + private let _MuteExpires_Days_zero: String + private let _MuteExpires_Days_one: String + private let _MuteExpires_Days_two: String + private let _MuteExpires_Days_few: String + private let _MuteExpires_Days_many: String + private let _MuteExpires_Days_other: String + public func MuteExpires_Days(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._MuteExpires_Days_zero, "\(value)") + case .one: + return String(format: self._MuteExpires_Days_one, "\(value)") + case .two: + return String(format: self._MuteExpires_Days_two, "\(value)") + case .few: + return String(format: self._MuteExpires_Days_few, "\(value)") + case .many: + return String(format: self._MuteExpires_Days_many, "\(value)") + case .other: + return String(format: self._MuteExpires_Days_other, "\(value)") + } + } + private let _MessageTimer_Days_zero: String + private let _MessageTimer_Days_one: String + private let _MessageTimer_Days_two: String + private let _MessageTimer_Days_few: String + private let _MessageTimer_Days_many: String + private let _MessageTimer_Days_other: String + public func MessageTimer_Days(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._MessageTimer_Days_zero, "\(value)") + case .one: + return String(format: self._MessageTimer_Days_one, "\(value)") + case .two: + return String(format: self._MessageTimer_Days_two, "\(value)") + case .few: + return String(format: self._MessageTimer_Days_few, "\(value)") + case .many: + return String(format: self._MessageTimer_Days_many, "\(value)") + case .other: + return String(format: self._MessageTimer_Days_other, "\(value)") + } + } + private let _Conversation_StatusMembers_zero: String + private let _Conversation_StatusMembers_one: String + private let _Conversation_StatusMembers_two: String + private let _Conversation_StatusMembers_few: String + private let _Conversation_StatusMembers_many: String + private let _Conversation_StatusMembers_other: String + public func Conversation_StatusMembers(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._Conversation_StatusMembers_zero, "\(value)") + case .one: + return String(format: self._Conversation_StatusMembers_one, "\(value)") + case .two: + return String(format: self._Conversation_StatusMembers_two, "\(value)") + case .few: + return String(format: self._Conversation_StatusMembers_few, "\(value)") + case .many: + return String(format: self._Conversation_StatusMembers_many, "\(value)") + case .other: + return String(format: self._Conversation_StatusMembers_other, "\(value)") + } + } + private let _MuteExpires_Hours_zero: String + private let _MuteExpires_Hours_one: String + private let _MuteExpires_Hours_two: String + private let _MuteExpires_Hours_few: String + private let _MuteExpires_Hours_many: String + private let _MuteExpires_Hours_other: String + public func MuteExpires_Hours(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._MuteExpires_Hours_zero, "\(value)") + case .one: + return String(format: self._MuteExpires_Hours_one, "\(value)") + case .two: + return String(format: self._MuteExpires_Hours_two, "\(value)") + case .few: + return String(format: self._MuteExpires_Hours_few, "\(value)") + case .many: + return String(format: self._MuteExpires_Hours_many, "\(value)") + case .other: + return String(format: self._MuteExpires_Hours_other, "\(value)") + } + } + private let _Call_ShortMinutes_zero: String + private let _Call_ShortMinutes_one: String + private let _Call_ShortMinutes_two: String + private let _Call_ShortMinutes_few: String + private let _Call_ShortMinutes_many: String + private let _Call_ShortMinutes_other: String + public func Call_ShortMinutes(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._Call_ShortMinutes_zero, "\(value)") + case .one: + return String(format: self._Call_ShortMinutes_one, "\(value)") + case .two: + return String(format: self._Call_ShortMinutes_two, "\(value)") + case .few: + return String(format: self._Call_ShortMinutes_few, "\(value)") + case .many: + return String(format: self._Call_ShortMinutes_many, "\(value)") + case .other: + return String(format: self._Call_ShortMinutes_other, "\(value)") + } + } + private let _Notifications_ExceptionMuteExpires_Hours_zero: String + private let _Notifications_ExceptionMuteExpires_Hours_one: String + private let _Notifications_ExceptionMuteExpires_Hours_two: String + private let _Notifications_ExceptionMuteExpires_Hours_few: String + private let _Notifications_ExceptionMuteExpires_Hours_many: String + private let _Notifications_ExceptionMuteExpires_Hours_other: String + public func Notifications_ExceptionMuteExpires_Hours(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._Notifications_ExceptionMuteExpires_Hours_zero, "\(value)") + case .one: + return String(format: self._Notifications_ExceptionMuteExpires_Hours_one, "\(value)") + case .two: + return String(format: self._Notifications_ExceptionMuteExpires_Hours_two, "\(value)") + case .few: + return String(format: self._Notifications_ExceptionMuteExpires_Hours_few, "\(value)") + case .many: + return String(format: self._Notifications_ExceptionMuteExpires_Hours_many, "\(value)") + case .other: + return String(format: self._Notifications_ExceptionMuteExpires_Hours_other, "\(value)") + } + } + private let _MessageTimer_ShortMinutes_zero: String + private let _MessageTimer_ShortMinutes_one: String + private let _MessageTimer_ShortMinutes_two: String + private let _MessageTimer_ShortMinutes_few: String + private let _MessageTimer_ShortMinutes_many: String + private let _MessageTimer_ShortMinutes_other: String + public func MessageTimer_ShortMinutes(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._MessageTimer_ShortMinutes_zero, "\(value)") + case .one: + return String(format: self._MessageTimer_ShortMinutes_one, "\(value)") + case .two: + return String(format: self._MessageTimer_ShortMinutes_two, "\(value)") + case .few: + return String(format: self._MessageTimer_ShortMinutes_few, "\(value)") + case .many: + return String(format: self._MessageTimer_ShortMinutes_many, "\(value)") + case .other: + return String(format: self._MessageTimer_ShortMinutes_other, "\(value)") + } + } + private let _ForwardedVideos_zero: String + private let _ForwardedVideos_one: String + private let _ForwardedVideos_two: String + private let _ForwardedVideos_few: String + private let _ForwardedVideos_many: String + private let _ForwardedVideos_other: String + public func ForwardedVideos(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._ForwardedVideos_zero, "\(value)") + case .one: + return String(format: self._ForwardedVideos_one, "\(value)") + case .two: + return String(format: self._ForwardedVideos_two, "\(value)") + case .few: + return String(format: self._ForwardedVideos_few, "\(value)") + case .many: + return String(format: self._ForwardedVideos_many, "\(value)") + case .other: + return String(format: self._ForwardedVideos_other, "\(value)") + } + } + private let _Call_ShortSeconds_zero: String + private let _Call_ShortSeconds_one: String + private let _Call_ShortSeconds_two: String + private let _Call_ShortSeconds_few: String + private let _Call_ShortSeconds_many: String + private let _Call_ShortSeconds_other: String + public func Call_ShortSeconds(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._Call_ShortSeconds_zero, "\(value)") + case .one: + return String(format: self._Call_ShortSeconds_one, "\(value)") + case .two: + return String(format: self._Call_ShortSeconds_two, "\(value)") + case .few: + return String(format: self._Call_ShortSeconds_few, "\(value)") + case .many: + return String(format: self._Call_ShortSeconds_many, "\(value)") + case .other: + return String(format: self._Call_ShortSeconds_other, "\(value)") + } + } + private let _LastSeen_MinutesAgo_zero: String + private let _LastSeen_MinutesAgo_one: String + private let _LastSeen_MinutesAgo_two: String + private let _LastSeen_MinutesAgo_few: String + private let _LastSeen_MinutesAgo_many: String + private let _LastSeen_MinutesAgo_other: String + public func LastSeen_MinutesAgo(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._LastSeen_MinutesAgo_zero, "\(value)") + case .one: + return String(format: self._LastSeen_MinutesAgo_one, "\(value)") + case .two: + return String(format: self._LastSeen_MinutesAgo_two, "\(value)") + case .few: + return String(format: self._LastSeen_MinutesAgo_few, "\(value)") + case .many: + return String(format: self._LastSeen_MinutesAgo_many, "\(value)") + case .other: + return String(format: self._LastSeen_MinutesAgo_other, "\(value)") + } + } + private let _Media_ShareVideo_zero: String + private let _Media_ShareVideo_one: String + private let _Media_ShareVideo_two: String + private let _Media_ShareVideo_few: String + private let _Media_ShareVideo_many: String + private let _Media_ShareVideo_other: String + public func Media_ShareVideo(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._Media_ShareVideo_zero, "\(value)") + case .one: + return String(format: self._Media_ShareVideo_one, "\(value)") + case .two: + return String(format: self._Media_ShareVideo_two, "\(value)") + case .few: + return String(format: self._Media_ShareVideo_few, "\(value)") + case .many: + return String(format: self._Media_ShareVideo_many, "\(value)") + case .other: + return String(format: self._Media_ShareVideo_other, "\(value)") + } + } + private let _Notification_GameScoreSelfExtended_zero: String + private let _Notification_GameScoreSelfExtended_one: String + private let _Notification_GameScoreSelfExtended_two: String + private let _Notification_GameScoreSelfExtended_few: String + private let _Notification_GameScoreSelfExtended_many: String + private let _Notification_GameScoreSelfExtended_other: String + public func Notification_GameScoreSelfExtended(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._Notification_GameScoreSelfExtended_zero, "\(value)") + case .one: + return String(format: self._Notification_GameScoreSelfExtended_one, "\(value)") + case .two: + return String(format: self._Notification_GameScoreSelfExtended_two, "\(value)") + case .few: + return String(format: self._Notification_GameScoreSelfExtended_few, "\(value)") + case .many: + return String(format: self._Notification_GameScoreSelfExtended_many, "\(value)") + case .other: + return String(format: self._Notification_GameScoreSelfExtended_other, "\(value)") + } + } private let _SharedMedia_DeleteItemsConfirmation_zero: String private let _SharedMedia_DeleteItemsConfirmation_one: String private let _SharedMedia_DeleteItemsConfirmation_two: String @@ -5439,26 +4758,48 @@ public final class PresentationStrings { return String(format: self._SharedMedia_DeleteItemsConfirmation_other, "\(value)") } } - private let _ForwardedPhotos_zero: String - private let _ForwardedPhotos_one: String - private let _ForwardedPhotos_two: String - private let _ForwardedPhotos_few: String - private let _ForwardedPhotos_many: String - private let _ForwardedPhotos_other: String - public func ForwardedPhotos(_ value: Int32) -> String { + private let _MessageTimer_ShortSeconds_zero: String + private let _MessageTimer_ShortSeconds_one: String + private let _MessageTimer_ShortSeconds_two: String + private let _MessageTimer_ShortSeconds_few: String + private let _MessageTimer_ShortSeconds_many: String + private let _MessageTimer_ShortSeconds_other: String + public func MessageTimer_ShortSeconds(_ value: Int32) -> String { switch presentationStringsPluralizationForm(self.lc, value) { case .zero: - return String(format: self._ForwardedPhotos_zero, "\(value)") + return String(format: self._MessageTimer_ShortSeconds_zero, "\(value)") case .one: - return String(format: self._ForwardedPhotos_one, "\(value)") + return String(format: self._MessageTimer_ShortSeconds_one, "\(value)") case .two: - return String(format: self._ForwardedPhotos_two, "\(value)") + return String(format: self._MessageTimer_ShortSeconds_two, "\(value)") case .few: - return String(format: self._ForwardedPhotos_few, "\(value)") + return String(format: self._MessageTimer_ShortSeconds_few, "\(value)") case .many: - return String(format: self._ForwardedPhotos_many, "\(value)") + return String(format: self._MessageTimer_ShortSeconds_many, "\(value)") case .other: - return String(format: self._ForwardedPhotos_other, "\(value)") + return String(format: self._MessageTimer_ShortSeconds_other, "\(value)") + } + } + private let _Conversation_StatusSubscribers_zero: String + private let _Conversation_StatusSubscribers_one: String + private let _Conversation_StatusSubscribers_two: String + private let _Conversation_StatusSubscribers_few: String + private let _Conversation_StatusSubscribers_many: String + private let _Conversation_StatusSubscribers_other: String + public func Conversation_StatusSubscribers(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._Conversation_StatusSubscribers_zero, "\(value)") + case .one: + return String(format: self._Conversation_StatusSubscribers_one, "\(value)") + case .two: + return String(format: self._Conversation_StatusSubscribers_two, "\(value)") + case .few: + return String(format: self._Conversation_StatusSubscribers_few, "\(value)") + case .many: + return String(format: self._Conversation_StatusSubscribers_many, "\(value)") + case .other: + return String(format: self._Conversation_StatusSubscribers_other, "\(value)") } } private let _MessageTimer_ShortDays_zero: String @@ -5483,26 +4824,686 @@ public final class PresentationStrings { return String(format: self._MessageTimer_ShortDays_other, "\(value)") } } - private let _LiveLocation_MenuChatsCount_zero: String - private let _LiveLocation_MenuChatsCount_one: String - private let _LiveLocation_MenuChatsCount_two: String - private let _LiveLocation_MenuChatsCount_few: String - private let _LiveLocation_MenuChatsCount_many: String - private let _LiveLocation_MenuChatsCount_other: String - public func LiveLocation_MenuChatsCount(_ value: Int32) -> String { + private let _StickerPack_AddStickerCount_zero: String + private let _StickerPack_AddStickerCount_one: String + private let _StickerPack_AddStickerCount_two: String + private let _StickerPack_AddStickerCount_few: String + private let _StickerPack_AddStickerCount_many: String + private let _StickerPack_AddStickerCount_other: String + public func StickerPack_AddStickerCount(_ value: Int32) -> String { switch presentationStringsPluralizationForm(self.lc, value) { case .zero: - return String(format: self._LiveLocation_MenuChatsCount_zero, "\(value)") + return String(format: self._StickerPack_AddStickerCount_zero, "\(value)") case .one: - return String(format: self._LiveLocation_MenuChatsCount_one, "\(value)") + return String(format: self._StickerPack_AddStickerCount_one, "\(value)") case .two: - return String(format: self._LiveLocation_MenuChatsCount_two, "\(value)") + return String(format: self._StickerPack_AddStickerCount_two, "\(value)") case .few: - return String(format: self._LiveLocation_MenuChatsCount_few, "\(value)") + return String(format: self._StickerPack_AddStickerCount_few, "\(value)") case .many: - return String(format: self._LiveLocation_MenuChatsCount_many, "\(value)") + return String(format: self._StickerPack_AddStickerCount_many, "\(value)") case .other: - return String(format: self._LiveLocation_MenuChatsCount_other, "\(value)") + return String(format: self._StickerPack_AddStickerCount_other, "\(value)") + } + } + private let _SharedMedia_Link_zero: String + private let _SharedMedia_Link_one: String + private let _SharedMedia_Link_two: String + private let _SharedMedia_Link_few: String + private let _SharedMedia_Link_many: String + private let _SharedMedia_Link_other: String + public func SharedMedia_Link(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._SharedMedia_Link_zero, "\(value)") + case .one: + return String(format: self._SharedMedia_Link_one, "\(value)") + case .two: + return String(format: self._SharedMedia_Link_two, "\(value)") + case .few: + return String(format: self._SharedMedia_Link_few, "\(value)") + case .many: + return String(format: self._SharedMedia_Link_many, "\(value)") + case .other: + return String(format: self._SharedMedia_Link_other, "\(value)") + } + } + private let _MessageTimer_Seconds_zero: String + private let _MessageTimer_Seconds_one: String + private let _MessageTimer_Seconds_two: String + private let _MessageTimer_Seconds_few: String + private let _MessageTimer_Seconds_many: String + private let _MessageTimer_Seconds_other: String + public func MessageTimer_Seconds(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._MessageTimer_Seconds_zero, "\(value)") + case .one: + return String(format: self._MessageTimer_Seconds_one, "\(value)") + case .two: + return String(format: self._MessageTimer_Seconds_two, "\(value)") + case .few: + return String(format: self._MessageTimer_Seconds_few, "\(value)") + case .many: + return String(format: self._MessageTimer_Seconds_many, "\(value)") + case .other: + return String(format: self._MessageTimer_Seconds_other, "\(value)") + } + } + private let _ServiceMessage_GameScoreSimple_zero: String + private let _ServiceMessage_GameScoreSimple_one: String + private let _ServiceMessage_GameScoreSimple_two: String + private let _ServiceMessage_GameScoreSimple_few: String + private let _ServiceMessage_GameScoreSimple_many: String + private let _ServiceMessage_GameScoreSimple_other: String + public func ServiceMessage_GameScoreSimple(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._ServiceMessage_GameScoreSimple_zero, "\(value)") + case .one: + return String(format: self._ServiceMessage_GameScoreSimple_one, "\(value)") + case .two: + return String(format: self._ServiceMessage_GameScoreSimple_two, "\(value)") + case .few: + return String(format: self._ServiceMessage_GameScoreSimple_few, "\(value)") + case .many: + return String(format: self._ServiceMessage_GameScoreSimple_many, "\(value)") + case .other: + return String(format: self._ServiceMessage_GameScoreSimple_other, "\(value)") + } + } + private let _ForwardedPhotos_zero: String + private let _ForwardedPhotos_one: String + private let _ForwardedPhotos_two: String + private let _ForwardedPhotos_few: String + private let _ForwardedPhotos_many: String + private let _ForwardedPhotos_other: String + public func ForwardedPhotos(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._ForwardedPhotos_zero, "\(value)") + case .one: + return String(format: self._ForwardedPhotos_one, "\(value)") + case .two: + return String(format: self._ForwardedPhotos_two, "\(value)") + case .few: + return String(format: self._ForwardedPhotos_few, "\(value)") + case .many: + return String(format: self._ForwardedPhotos_many, "\(value)") + case .other: + return String(format: self._ForwardedPhotos_other, "\(value)") + } + } + private let _MessageTimer_Years_zero: String + private let _MessageTimer_Years_one: String + private let _MessageTimer_Years_two: String + private let _MessageTimer_Years_few: String + private let _MessageTimer_Years_many: String + private let _MessageTimer_Years_other: String + public func MessageTimer_Years(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._MessageTimer_Years_zero, "\(value)") + case .one: + return String(format: self._MessageTimer_Years_one, "\(value)") + case .two: + return String(format: self._MessageTimer_Years_two, "\(value)") + case .few: + return String(format: self._MessageTimer_Years_few, "\(value)") + case .many: + return String(format: self._MessageTimer_Years_many, "\(value)") + case .other: + return String(format: self._MessageTimer_Years_other, "\(value)") + } + } + private let _Forward_ConfirmMultipleFiles_zero: String + private let _Forward_ConfirmMultipleFiles_one: String + private let _Forward_ConfirmMultipleFiles_two: String + private let _Forward_ConfirmMultipleFiles_few: String + private let _Forward_ConfirmMultipleFiles_many: String + private let _Forward_ConfirmMultipleFiles_other: String + public func Forward_ConfirmMultipleFiles(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._Forward_ConfirmMultipleFiles_zero, "\(value)") + case .one: + return String(format: self._Forward_ConfirmMultipleFiles_one, "\(value)") + case .two: + return String(format: self._Forward_ConfirmMultipleFiles_two, "\(value)") + case .few: + return String(format: self._Forward_ConfirmMultipleFiles_few, "\(value)") + case .many: + return String(format: self._Forward_ConfirmMultipleFiles_many, "\(value)") + case .other: + return String(format: self._Forward_ConfirmMultipleFiles_other, "\(value)") + } + } + private let _MessageTimer_ShortWeeks_zero: String + private let _MessageTimer_ShortWeeks_one: String + private let _MessageTimer_ShortWeeks_two: String + private let _MessageTimer_ShortWeeks_few: String + private let _MessageTimer_ShortWeeks_many: String + private let _MessageTimer_ShortWeeks_other: String + public func MessageTimer_ShortWeeks(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._MessageTimer_ShortWeeks_zero, "\(value)") + case .one: + return String(format: self._MessageTimer_ShortWeeks_one, "\(value)") + case .two: + return String(format: self._MessageTimer_ShortWeeks_two, "\(value)") + case .few: + return String(format: self._MessageTimer_ShortWeeks_few, "\(value)") + case .many: + return String(format: self._MessageTimer_ShortWeeks_many, "\(value)") + case .other: + return String(format: self._MessageTimer_ShortWeeks_other, "\(value)") + } + } + private let _DialogList_LiveLocationChatsCount_zero: String + private let _DialogList_LiveLocationChatsCount_one: String + private let _DialogList_LiveLocationChatsCount_two: String + private let _DialogList_LiveLocationChatsCount_few: String + private let _DialogList_LiveLocationChatsCount_many: String + private let _DialogList_LiveLocationChatsCount_other: String + public func DialogList_LiveLocationChatsCount(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._DialogList_LiveLocationChatsCount_zero, "\(value)") + case .one: + return String(format: self._DialogList_LiveLocationChatsCount_one, "\(value)") + case .two: + return String(format: self._DialogList_LiveLocationChatsCount_two, "\(value)") + case .few: + return String(format: self._DialogList_LiveLocationChatsCount_few, "\(value)") + case .many: + return String(format: self._DialogList_LiveLocationChatsCount_many, "\(value)") + case .other: + return String(format: self._DialogList_LiveLocationChatsCount_other, "\(value)") + } + } + private let _Notifications_ExceptionMuteExpires_Minutes_zero: String + private let _Notifications_ExceptionMuteExpires_Minutes_one: String + private let _Notifications_ExceptionMuteExpires_Minutes_two: String + private let _Notifications_ExceptionMuteExpires_Minutes_few: String + private let _Notifications_ExceptionMuteExpires_Minutes_many: String + private let _Notifications_ExceptionMuteExpires_Minutes_other: String + public func Notifications_ExceptionMuteExpires_Minutes(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._Notifications_ExceptionMuteExpires_Minutes_zero, "\(value)") + case .one: + return String(format: self._Notifications_ExceptionMuteExpires_Minutes_one, "\(value)") + case .two: + return String(format: self._Notifications_ExceptionMuteExpires_Minutes_two, "\(value)") + case .few: + return String(format: self._Notifications_ExceptionMuteExpires_Minutes_few, "\(value)") + case .many: + return String(format: self._Notifications_ExceptionMuteExpires_Minutes_many, "\(value)") + case .other: + return String(format: self._Notifications_ExceptionMuteExpires_Minutes_other, "\(value)") + } + } + private let _MessageTimer_Months_zero: String + private let _MessageTimer_Months_one: String + private let _MessageTimer_Months_two: String + private let _MessageTimer_Months_few: String + private let _MessageTimer_Months_many: String + private let _MessageTimer_Months_other: String + public func MessageTimer_Months(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._MessageTimer_Months_zero, "\(value)") + case .one: + return String(format: self._MessageTimer_Months_one, "\(value)") + case .two: + return String(format: self._MessageTimer_Months_two, "\(value)") + case .few: + return String(format: self._MessageTimer_Months_few, "\(value)") + case .many: + return String(format: self._MessageTimer_Months_many, "\(value)") + case .other: + return String(format: self._MessageTimer_Months_other, "\(value)") + } + } + private let _QuickSend_Photos_zero: String + private let _QuickSend_Photos_one: String + private let _QuickSend_Photos_two: String + private let _QuickSend_Photos_few: String + private let _QuickSend_Photos_many: String + private let _QuickSend_Photos_other: String + public func QuickSend_Photos(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._QuickSend_Photos_zero, "\(value)") + case .one: + return String(format: self._QuickSend_Photos_one, "\(value)") + case .two: + return String(format: self._QuickSend_Photos_two, "\(value)") + case .few: + return String(format: self._QuickSend_Photos_few, "\(value)") + case .many: + return String(format: self._QuickSend_Photos_many, "\(value)") + case .other: + return String(format: self._QuickSend_Photos_other, "\(value)") + } + } + private let _PrivacyLastSeenSettings_AddUsers_zero: String + private let _PrivacyLastSeenSettings_AddUsers_one: String + private let _PrivacyLastSeenSettings_AddUsers_two: String + private let _PrivacyLastSeenSettings_AddUsers_few: String + private let _PrivacyLastSeenSettings_AddUsers_many: String + private let _PrivacyLastSeenSettings_AddUsers_other: String + public func PrivacyLastSeenSettings_AddUsers(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._PrivacyLastSeenSettings_AddUsers_zero, "\(value)") + case .one: + return String(format: self._PrivacyLastSeenSettings_AddUsers_one, "\(value)") + case .two: + return String(format: self._PrivacyLastSeenSettings_AddUsers_two, "\(value)") + case .few: + return String(format: self._PrivacyLastSeenSettings_AddUsers_few, "\(value)") + case .many: + return String(format: self._PrivacyLastSeenSettings_AddUsers_many, "\(value)") + case .other: + return String(format: self._PrivacyLastSeenSettings_AddUsers_other, "\(value)") + } + } + private let _InviteText_ContactsCountText_zero: String + private let _InviteText_ContactsCountText_one: String + private let _InviteText_ContactsCountText_two: String + private let _InviteText_ContactsCountText_few: String + private let _InviteText_ContactsCountText_many: String + private let _InviteText_ContactsCountText_other: String + public func InviteText_ContactsCountText(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._InviteText_ContactsCountText_zero, "\(value)") + case .one: + return String(format: self._InviteText_ContactsCountText_one, "\(value)") + case .two: + return String(format: self._InviteText_ContactsCountText_two, "\(value)") + case .few: + return String(format: self._InviteText_ContactsCountText_few, "\(value)") + case .many: + return String(format: self._InviteText_ContactsCountText_many, "\(value)") + case .other: + return String(format: self._InviteText_ContactsCountText_other, "\(value)") + } + } + private let _SharedMedia_Generic_zero: String + private let _SharedMedia_Generic_one: String + private let _SharedMedia_Generic_two: String + private let _SharedMedia_Generic_few: String + private let _SharedMedia_Generic_many: String + private let _SharedMedia_Generic_other: String + public func SharedMedia_Generic(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._SharedMedia_Generic_zero, "\(value)") + case .one: + return String(format: self._SharedMedia_Generic_one, "\(value)") + case .two: + return String(format: self._SharedMedia_Generic_two, "\(value)") + case .few: + return String(format: self._SharedMedia_Generic_few, "\(value)") + case .many: + return String(format: self._SharedMedia_Generic_many, "\(value)") + case .other: + return String(format: self._SharedMedia_Generic_other, "\(value)") + } + } + private let _StickerPack_StickerCount_zero: String + private let _StickerPack_StickerCount_one: String + private let _StickerPack_StickerCount_two: String + private let _StickerPack_StickerCount_few: String + private let _StickerPack_StickerCount_many: String + private let _StickerPack_StickerCount_other: String + public func StickerPack_StickerCount(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._StickerPack_StickerCount_zero, "\(value)") + case .one: + return String(format: self._StickerPack_StickerCount_one, "\(value)") + case .two: + return String(format: self._StickerPack_StickerCount_two, "\(value)") + case .few: + return String(format: self._StickerPack_StickerCount_few, "\(value)") + case .many: + return String(format: self._StickerPack_StickerCount_many, "\(value)") + case .other: + return String(format: self._StickerPack_StickerCount_other, "\(value)") + } + } + private let _PasscodeSettings_FailedAttempts_zero: String + private let _PasscodeSettings_FailedAttempts_one: String + private let _PasscodeSettings_FailedAttempts_two: String + private let _PasscodeSettings_FailedAttempts_few: String + private let _PasscodeSettings_FailedAttempts_many: String + private let _PasscodeSettings_FailedAttempts_other: String + public func PasscodeSettings_FailedAttempts(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._PasscodeSettings_FailedAttempts_zero, "\(value)") + case .one: + return String(format: self._PasscodeSettings_FailedAttempts_one, "\(value)") + case .two: + return String(format: self._PasscodeSettings_FailedAttempts_two, "\(value)") + case .few: + return String(format: self._PasscodeSettings_FailedAttempts_few, "\(value)") + case .many: + return String(format: self._PasscodeSettings_FailedAttempts_many, "\(value)") + case .other: + return String(format: self._PasscodeSettings_FailedAttempts_other, "\(value)") + } + } + private let _Map_ETAMinutes_zero: String + private let _Map_ETAMinutes_one: String + private let _Map_ETAMinutes_two: String + private let _Map_ETAMinutes_few: String + private let _Map_ETAMinutes_many: String + private let _Map_ETAMinutes_other: String + public func Map_ETAMinutes(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._Map_ETAMinutes_zero, "\(value)") + case .one: + return String(format: self._Map_ETAMinutes_one, "\(value)") + case .two: + return String(format: self._Map_ETAMinutes_two, "\(value)") + case .few: + return String(format: self._Map_ETAMinutes_few, "\(value)") + case .many: + return String(format: self._Map_ETAMinutes_many, "\(value)") + case .other: + return String(format: self._Map_ETAMinutes_other, "\(value)") + } + } + private let _Watch_LastSeen_HoursAgo_zero: String + private let _Watch_LastSeen_HoursAgo_one: String + private let _Watch_LastSeen_HoursAgo_two: String + private let _Watch_LastSeen_HoursAgo_few: String + private let _Watch_LastSeen_HoursAgo_many: String + private let _Watch_LastSeen_HoursAgo_other: String + public func Watch_LastSeen_HoursAgo(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._Watch_LastSeen_HoursAgo_zero, "\(value)") + case .one: + return String(format: self._Watch_LastSeen_HoursAgo_one, "\(value)") + case .two: + return String(format: self._Watch_LastSeen_HoursAgo_two, "\(value)") + case .few: + return String(format: self._Watch_LastSeen_HoursAgo_few, "\(value)") + case .many: + return String(format: self._Watch_LastSeen_HoursAgo_many, "\(value)") + case .other: + return String(format: self._Watch_LastSeen_HoursAgo_other, "\(value)") + } + } + private let _SharedMedia_Video_zero: String + private let _SharedMedia_Video_one: String + private let _SharedMedia_Video_two: String + private let _SharedMedia_Video_few: String + private let _SharedMedia_Video_many: String + private let _SharedMedia_Video_other: String + public func SharedMedia_Video(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._SharedMedia_Video_zero, "\(value)") + case .one: + return String(format: self._SharedMedia_Video_one, "\(value)") + case .two: + return String(format: self._SharedMedia_Video_two, "\(value)") + case .few: + return String(format: self._SharedMedia_Video_few, "\(value)") + case .many: + return String(format: self._SharedMedia_Video_many, "\(value)") + case .other: + return String(format: self._SharedMedia_Video_other, "\(value)") + } + } + private let _ForwardedVideoMessages_zero: String + private let _ForwardedVideoMessages_one: String + private let _ForwardedVideoMessages_two: String + private let _ForwardedVideoMessages_few: String + private let _ForwardedVideoMessages_many: String + private let _ForwardedVideoMessages_other: String + public func ForwardedVideoMessages(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._ForwardedVideoMessages_zero, "\(value)") + case .one: + return String(format: self._ForwardedVideoMessages_one, "\(value)") + case .two: + return String(format: self._ForwardedVideoMessages_two, "\(value)") + case .few: + return String(format: self._ForwardedVideoMessages_few, "\(value)") + case .many: + return String(format: self._ForwardedVideoMessages_many, "\(value)") + case .other: + return String(format: self._ForwardedVideoMessages_other, "\(value)") + } + } + private let _Passport_Scans_zero: String + private let _Passport_Scans_one: String + private let _Passport_Scans_two: String + private let _Passport_Scans_few: String + private let _Passport_Scans_many: String + private let _Passport_Scans_other: String + public func Passport_Scans(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._Passport_Scans_zero, "\(value)") + case .one: + return String(format: self._Passport_Scans_one, "\(value)") + case .two: + return String(format: self._Passport_Scans_two, "\(value)") + case .few: + return String(format: self._Passport_Scans_few, "\(value)") + case .many: + return String(format: self._Passport_Scans_many, "\(value)") + case .other: + return String(format: self._Passport_Scans_other, "\(value)") + } + } + private let _Watch_UserInfo_Mute_zero: String + private let _Watch_UserInfo_Mute_one: String + private let _Watch_UserInfo_Mute_two: String + private let _Watch_UserInfo_Mute_few: String + private let _Watch_UserInfo_Mute_many: String + private let _Watch_UserInfo_Mute_other: String + public func Watch_UserInfo_Mute(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._Watch_UserInfo_Mute_zero, "\(value)") + case .one: + return String(format: self._Watch_UserInfo_Mute_one, "\(value)") + case .two: + return String(format: self._Watch_UserInfo_Mute_two, "\(value)") + case .few: + return String(format: self._Watch_UserInfo_Mute_few, "\(value)") + case .many: + return String(format: self._Watch_UserInfo_Mute_many, "\(value)") + case .other: + return String(format: self._Watch_UserInfo_Mute_other, "\(value)") + } + } + private let _SharedMedia_File_zero: String + private let _SharedMedia_File_one: String + private let _SharedMedia_File_two: String + private let _SharedMedia_File_few: String + private let _SharedMedia_File_many: String + private let _SharedMedia_File_other: String + public func SharedMedia_File(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._SharedMedia_File_zero, "\(value)") + case .one: + return String(format: self._SharedMedia_File_one, "\(value)") + case .two: + return String(format: self._SharedMedia_File_two, "\(value)") + case .few: + return String(format: self._SharedMedia_File_few, "\(value)") + case .many: + return String(format: self._SharedMedia_File_many, "\(value)") + case .other: + return String(format: self._SharedMedia_File_other, "\(value)") + } + } + private let _Media_SharePhoto_zero: String + private let _Media_SharePhoto_one: String + private let _Media_SharePhoto_two: String + private let _Media_SharePhoto_few: String + private let _Media_SharePhoto_many: String + private let _Media_SharePhoto_other: String + public func Media_SharePhoto(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._Media_SharePhoto_zero, "\(value)") + case .one: + return String(format: self._Media_SharePhoto_one, "\(value)") + case .two: + return String(format: self._Media_SharePhoto_two, "\(value)") + case .few: + return String(format: self._Media_SharePhoto_few, "\(value)") + case .many: + return String(format: self._Media_SharePhoto_many, "\(value)") + case .other: + return String(format: self._Media_SharePhoto_other, "\(value)") + } + } + private let _StickerPack_AddMaskCount_zero: String + private let _StickerPack_AddMaskCount_one: String + private let _StickerPack_AddMaskCount_two: String + private let _StickerPack_AddMaskCount_few: String + private let _StickerPack_AddMaskCount_many: String + private let _StickerPack_AddMaskCount_other: String + public func StickerPack_AddMaskCount(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._StickerPack_AddMaskCount_zero, "\(value)") + case .one: + return String(format: self._StickerPack_AddMaskCount_one, "\(value)") + case .two: + return String(format: self._StickerPack_AddMaskCount_two, "\(value)") + case .few: + return String(format: self._StickerPack_AddMaskCount_few, "\(value)") + case .many: + return String(format: self._StickerPack_AddMaskCount_many, "\(value)") + case .other: + return String(format: self._StickerPack_AddMaskCount_other, "\(value)") + } + } + private let _UserCount_zero: String + private let _UserCount_one: String + private let _UserCount_two: String + private let _UserCount_few: String + private let _UserCount_many: String + private let _UserCount_other: String + public func UserCount(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._UserCount_zero, "\(value)") + case .one: + return String(format: self._UserCount_one, "\(value)") + case .two: + return String(format: self._UserCount_two, "\(value)") + case .few: + return String(format: self._UserCount_few, "\(value)") + case .many: + return String(format: self._UserCount_many, "\(value)") + case .other: + return String(format: self._UserCount_other, "\(value)") + } + } + private let _Conversation_LiveLocationMembersCount_zero: String + private let _Conversation_LiveLocationMembersCount_one: String + private let _Conversation_LiveLocationMembersCount_two: String + private let _Conversation_LiveLocationMembersCount_few: String + private let _Conversation_LiveLocationMembersCount_many: String + private let _Conversation_LiveLocationMembersCount_other: String + public func Conversation_LiveLocationMembersCount(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._Conversation_LiveLocationMembersCount_zero, "\(value)") + case .one: + return String(format: self._Conversation_LiveLocationMembersCount_one, "\(value)") + case .two: + return String(format: self._Conversation_LiveLocationMembersCount_two, "\(value)") + case .few: + return String(format: self._Conversation_LiveLocationMembersCount_few, "\(value)") + case .many: + return String(format: self._Conversation_LiveLocationMembersCount_many, "\(value)") + case .other: + return String(format: self._Conversation_LiveLocationMembersCount_other, "\(value)") + } + } + private let _Notifications_ExceptionMuteExpires_Days_zero: String + private let _Notifications_ExceptionMuteExpires_Days_one: String + private let _Notifications_ExceptionMuteExpires_Days_two: String + private let _Notifications_ExceptionMuteExpires_Days_few: String + private let _Notifications_ExceptionMuteExpires_Days_many: String + private let _Notifications_ExceptionMuteExpires_Days_other: String + public func Notifications_ExceptionMuteExpires_Days(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._Notifications_ExceptionMuteExpires_Days_zero, "\(value)") + case .one: + return String(format: self._Notifications_ExceptionMuteExpires_Days_one, "\(value)") + case .two: + return String(format: self._Notifications_ExceptionMuteExpires_Days_two, "\(value)") + case .few: + return String(format: self._Notifications_ExceptionMuteExpires_Days_few, "\(value)") + case .many: + return String(format: self._Notifications_ExceptionMuteExpires_Days_many, "\(value)") + case .other: + return String(format: self._Notifications_ExceptionMuteExpires_Days_other, "\(value)") + } + } + private let _MessageTimer_Hours_zero: String + private let _MessageTimer_Hours_one: String + private let _MessageTimer_Hours_two: String + private let _MessageTimer_Hours_few: String + private let _MessageTimer_Hours_many: String + private let _MessageTimer_Hours_other: String + public func MessageTimer_Hours(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._MessageTimer_Hours_zero, "\(value)") + case .one: + return String(format: self._MessageTimer_Hours_one, "\(value)") + case .two: + return String(format: self._MessageTimer_Hours_two, "\(value)") + case .few: + return String(format: self._MessageTimer_Hours_few, "\(value)") + case .many: + return String(format: self._MessageTimer_Hours_many, "\(value)") + case .other: + return String(format: self._MessageTimer_Hours_other, "\(value)") + } + } + private let _LiveLocationUpdated_MinutesAgo_zero: String + private let _LiveLocationUpdated_MinutesAgo_one: String + private let _LiveLocationUpdated_MinutesAgo_two: String + private let _LiveLocationUpdated_MinutesAgo_few: String + private let _LiveLocationUpdated_MinutesAgo_many: String + private let _LiveLocationUpdated_MinutesAgo_other: String + public func LiveLocationUpdated_MinutesAgo(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._LiveLocationUpdated_MinutesAgo_zero, "\(value)") + case .one: + return String(format: self._LiveLocationUpdated_MinutesAgo_one, "\(value)") + case .two: + return String(format: self._LiveLocationUpdated_MinutesAgo_two, "\(value)") + case .few: + return String(format: self._LiveLocationUpdated_MinutesAgo_few, "\(value)") + case .many: + return String(format: self._LiveLocationUpdated_MinutesAgo_many, "\(value)") + case .other: + return String(format: self._LiveLocationUpdated_MinutesAgo_other, "\(value)") } } @@ -6129,6 +6130,7 @@ public final class PresentationStrings { self._Notification_Kicked_r = extractArgumentRanges(self._Notification_Kicked) self.Channel_AdminLog_MessageRestrictedForever = getValue(dict, "Channel.AdminLog.MessageRestrictedForever") self.Passport_DeleteDocument = getValue(dict, "Passport.DeleteDocument") + self.Notifications_ExceptionsResetToDefaults = getValue(dict, "Notifications.ExceptionsResetToDefaults") self.ChannelInfo_DeleteChannelConfirmation = getValue(dict, "ChannelInfo.DeleteChannelConfirmation") self.Passport_Address_OneOfTypeBankStatement = getValue(dict, "Passport.Address.OneOfTypeBankStatement") self.Weekday_ShortSaturday = getValue(dict, "Weekday.ShortSaturday") @@ -6589,7 +6591,6 @@ public final class PresentationStrings { self._LOCKED_MESSAGE_r = extractArgumentRanges(self._LOCKED_MESSAGE) self.Conversation_ClearPrivateHistory = getValue(dict, "Conversation.ClearPrivateHistory") self.Conversation_ContextMenuShare = getValue(dict, "Conversation.ContextMenuShare") - self.Notifications_ExceptionsResetToDefaults = getValue(dict, "Notifications.ExceptionsResetToDefaults") self.Notifications_ExceptionsNone = getValue(dict, "Notifications.ExceptionsNone") self._Time_MonthOfYear_m6 = getValue(dict, "Time.MonthOfYear_m6") self._Time_MonthOfYear_m6_r = extractArgumentRanges(self._Time_MonthOfYear_m6) @@ -7029,6 +7030,7 @@ public final class PresentationStrings { self._Channel_AdminLog_MessageRemovedGroupStickerPack = getValue(dict, "Channel.AdminLog.MessageRemovedGroupStickerPack") self._Channel_AdminLog_MessageRemovedGroupStickerPack_r = extractArgumentRanges(self._Channel_AdminLog_MessageRemovedGroupStickerPack) self.PrivacyPolicy_DeclineTitle = getValue(dict, "PrivacyPolicy.DeclineTitle") + self.AuthSessions_PasswordPending = getValue(dict, "AuthSessions.PasswordPending") self.AccessDenied_VideoMessageCamera = getValue(dict, "AccessDenied.VideoMessageCamera") self.Privacy_ContactsSyncHelp = getValue(dict, "Privacy.ContactsSyncHelp") self.Conversation_Search = getValue(dict, "Conversation.Search") @@ -8044,522 +8046,522 @@ public final class PresentationStrings { self.PrivacySettings_PasscodeAndFaceId = getValue(dict, "PrivacySettings.PasscodeAndFaceId") self.Settings_ChatBackground = getValue(dict, "Settings.ChatBackground") self.Login_TermsOfServiceDecline = getValue(dict, "Login.TermsOfServiceDecline") - self._LastSeen_MinutesAgo_zero = getValueWithForm(dict, "LastSeen.MinutesAgo", .zero) - self._LastSeen_MinutesAgo_one = getValueWithForm(dict, "LastSeen.MinutesAgo", .one) - self._LastSeen_MinutesAgo_two = getValueWithForm(dict, "LastSeen.MinutesAgo", .two) - self._LastSeen_MinutesAgo_few = getValueWithForm(dict, "LastSeen.MinutesAgo", .few) - self._LastSeen_MinutesAgo_many = getValueWithForm(dict, "LastSeen.MinutesAgo", .many) - self._LastSeen_MinutesAgo_other = getValueWithForm(dict, "LastSeen.MinutesAgo", .other) - self._Passport_Scans_zero = getValueWithForm(dict, "Passport.Scans", .zero) - self._Passport_Scans_one = getValueWithForm(dict, "Passport.Scans", .one) - self._Passport_Scans_two = getValueWithForm(dict, "Passport.Scans", .two) - self._Passport_Scans_few = getValueWithForm(dict, "Passport.Scans", .few) - self._Passport_Scans_many = getValueWithForm(dict, "Passport.Scans", .many) - self._Passport_Scans_other = getValueWithForm(dict, "Passport.Scans", .other) - self._Notifications_Exceptions_zero = getValueWithForm(dict, "Notifications.Exceptions", .zero) - self._Notifications_Exceptions_one = getValueWithForm(dict, "Notifications.Exceptions", .one) - self._Notifications_Exceptions_two = getValueWithForm(dict, "Notifications.Exceptions", .two) - self._Notifications_Exceptions_few = getValueWithForm(dict, "Notifications.Exceptions", .few) - self._Notifications_Exceptions_many = getValueWithForm(dict, "Notifications.Exceptions", .many) - self._Notifications_Exceptions_other = getValueWithForm(dict, "Notifications.Exceptions", .other) - self._MuteExpires_Days_zero = getValueWithForm(dict, "MuteExpires.Days", .zero) - self._MuteExpires_Days_one = getValueWithForm(dict, "MuteExpires.Days", .one) - self._MuteExpires_Days_two = getValueWithForm(dict, "MuteExpires.Days", .two) - self._MuteExpires_Days_few = getValueWithForm(dict, "MuteExpires.Days", .few) - self._MuteExpires_Days_many = getValueWithForm(dict, "MuteExpires.Days", .many) - self._MuteExpires_Days_other = getValueWithForm(dict, "MuteExpires.Days", .other) - self._Watch_UserInfo_Mute_zero = getValueWithForm(dict, "Watch.UserInfo.Mute", .zero) - self._Watch_UserInfo_Mute_one = getValueWithForm(dict, "Watch.UserInfo.Mute", .one) - self._Watch_UserInfo_Mute_two = getValueWithForm(dict, "Watch.UserInfo.Mute", .two) - self._Watch_UserInfo_Mute_few = getValueWithForm(dict, "Watch.UserInfo.Mute", .few) - self._Watch_UserInfo_Mute_many = getValueWithForm(dict, "Watch.UserInfo.Mute", .many) - self._Watch_UserInfo_Mute_other = getValueWithForm(dict, "Watch.UserInfo.Mute", .other) - self._MessageTimer_Days_zero = getValueWithForm(dict, "MessageTimer.Days", .zero) - self._MessageTimer_Days_one = getValueWithForm(dict, "MessageTimer.Days", .one) - self._MessageTimer_Days_two = getValueWithForm(dict, "MessageTimer.Days", .two) - self._MessageTimer_Days_few = getValueWithForm(dict, "MessageTimer.Days", .few) - self._MessageTimer_Days_many = getValueWithForm(dict, "MessageTimer.Days", .many) - self._MessageTimer_Days_other = getValueWithForm(dict, "MessageTimer.Days", .other) - self._Map_ETAMinutes_zero = getValueWithForm(dict, "Map.ETAMinutes", .zero) - self._Map_ETAMinutes_one = getValueWithForm(dict, "Map.ETAMinutes", .one) - self._Map_ETAMinutes_two = getValueWithForm(dict, "Map.ETAMinutes", .two) - self._Map_ETAMinutes_few = getValueWithForm(dict, "Map.ETAMinutes", .few) - self._Map_ETAMinutes_many = getValueWithForm(dict, "Map.ETAMinutes", .many) - self._Map_ETAMinutes_other = getValueWithForm(dict, "Map.ETAMinutes", .other) - self._Contacts_ImportersCount_zero = getValueWithForm(dict, "Contacts.ImportersCount", .zero) - self._Contacts_ImportersCount_one = getValueWithForm(dict, "Contacts.ImportersCount", .one) - self._Contacts_ImportersCount_two = getValueWithForm(dict, "Contacts.ImportersCount", .two) - self._Contacts_ImportersCount_few = getValueWithForm(dict, "Contacts.ImportersCount", .few) - self._Contacts_ImportersCount_many = getValueWithForm(dict, "Contacts.ImportersCount", .many) - self._Contacts_ImportersCount_other = getValueWithForm(dict, "Contacts.ImportersCount", .other) - self._StickerPack_AddStickerCount_zero = getValueWithForm(dict, "StickerPack.AddStickerCount", .zero) - self._StickerPack_AddStickerCount_one = getValueWithForm(dict, "StickerPack.AddStickerCount", .one) - self._StickerPack_AddStickerCount_two = getValueWithForm(dict, "StickerPack.AddStickerCount", .two) - self._StickerPack_AddStickerCount_few = getValueWithForm(dict, "StickerPack.AddStickerCount", .few) - self._StickerPack_AddStickerCount_many = getValueWithForm(dict, "StickerPack.AddStickerCount", .many) - self._StickerPack_AddStickerCount_other = getValueWithForm(dict, "StickerPack.AddStickerCount", .other) - self._UserCount_zero = getValueWithForm(dict, "UserCount", .zero) - self._UserCount_one = getValueWithForm(dict, "UserCount", .one) - self._UserCount_two = getValueWithForm(dict, "UserCount", .two) - self._UserCount_few = getValueWithForm(dict, "UserCount", .few) - self._UserCount_many = getValueWithForm(dict, "UserCount", .many) - self._UserCount_other = getValueWithForm(dict, "UserCount", .other) - self._Notifications_ExceptionMuteExpires_Days_zero = getValueWithForm(dict, "Notifications.ExceptionMuteExpires.Days", .zero) - self._Notifications_ExceptionMuteExpires_Days_one = getValueWithForm(dict, "Notifications.ExceptionMuteExpires.Days", .one) - self._Notifications_ExceptionMuteExpires_Days_two = getValueWithForm(dict, "Notifications.ExceptionMuteExpires.Days", .two) - self._Notifications_ExceptionMuteExpires_Days_few = getValueWithForm(dict, "Notifications.ExceptionMuteExpires.Days", .few) - self._Notifications_ExceptionMuteExpires_Days_many = getValueWithForm(dict, "Notifications.ExceptionMuteExpires.Days", .many) - self._Notifications_ExceptionMuteExpires_Days_other = getValueWithForm(dict, "Notifications.ExceptionMuteExpires.Days", .other) - self._MuteFor_Hours_zero = getValueWithForm(dict, "MuteFor.Hours", .zero) - self._MuteFor_Hours_one = getValueWithForm(dict, "MuteFor.Hours", .one) - self._MuteFor_Hours_two = getValueWithForm(dict, "MuteFor.Hours", .two) - self._MuteFor_Hours_few = getValueWithForm(dict, "MuteFor.Hours", .few) - self._MuteFor_Hours_many = getValueWithForm(dict, "MuteFor.Hours", .many) - self._MuteFor_Hours_other = getValueWithForm(dict, "MuteFor.Hours", .other) - self._MessageTimer_Hours_zero = getValueWithForm(dict, "MessageTimer.Hours", .zero) - self._MessageTimer_Hours_one = getValueWithForm(dict, "MessageTimer.Hours", .one) - self._MessageTimer_Hours_two = getValueWithForm(dict, "MessageTimer.Hours", .two) - self._MessageTimer_Hours_few = getValueWithForm(dict, "MessageTimer.Hours", .few) - self._MessageTimer_Hours_many = getValueWithForm(dict, "MessageTimer.Hours", .many) - self._MessageTimer_Hours_other = getValueWithForm(dict, "MessageTimer.Hours", .other) - self._Media_SharePhoto_zero = getValueWithForm(dict, "Media.SharePhoto", .zero) - self._Media_SharePhoto_one = getValueWithForm(dict, "Media.SharePhoto", .one) - self._Media_SharePhoto_two = getValueWithForm(dict, "Media.SharePhoto", .two) - self._Media_SharePhoto_few = getValueWithForm(dict, "Media.SharePhoto", .few) - self._Media_SharePhoto_many = getValueWithForm(dict, "Media.SharePhoto", .many) - self._Media_SharePhoto_other = getValueWithForm(dict, "Media.SharePhoto", .other) - self._Map_ETAHours_zero = getValueWithForm(dict, "Map.ETAHours", .zero) - self._Map_ETAHours_one = getValueWithForm(dict, "Map.ETAHours", .one) - self._Map_ETAHours_two = getValueWithForm(dict, "Map.ETAHours", .two) - self._Map_ETAHours_few = getValueWithForm(dict, "Map.ETAHours", .few) - self._Map_ETAHours_many = getValueWithForm(dict, "Map.ETAHours", .many) - self._Map_ETAHours_other = getValueWithForm(dict, "Map.ETAHours", .other) - self._Conversation_StatusMembers_zero = getValueWithForm(dict, "Conversation.StatusMembers", .zero) - self._Conversation_StatusMembers_one = getValueWithForm(dict, "Conversation.StatusMembers", .one) - self._Conversation_StatusMembers_two = getValueWithForm(dict, "Conversation.StatusMembers", .two) - self._Conversation_StatusMembers_few = getValueWithForm(dict, "Conversation.StatusMembers", .few) - self._Conversation_StatusMembers_many = getValueWithForm(dict, "Conversation.StatusMembers", .many) - self._Conversation_StatusMembers_other = getValueWithForm(dict, "Conversation.StatusMembers", .other) - self._ServiceMessage_GameScoreSelfExtended_zero = getValueWithForm(dict, "ServiceMessage.GameScoreSelfExtended", .zero) - self._ServiceMessage_GameScoreSelfExtended_one = getValueWithForm(dict, "ServiceMessage.GameScoreSelfExtended", .one) - self._ServiceMessage_GameScoreSelfExtended_two = getValueWithForm(dict, "ServiceMessage.GameScoreSelfExtended", .two) - self._ServiceMessage_GameScoreSelfExtended_few = getValueWithForm(dict, "ServiceMessage.GameScoreSelfExtended", .few) - self._ServiceMessage_GameScoreSelfExtended_many = getValueWithForm(dict, "ServiceMessage.GameScoreSelfExtended", .many) - self._ServiceMessage_GameScoreSelfExtended_other = getValueWithForm(dict, "ServiceMessage.GameScoreSelfExtended", .other) - self._SharedMedia_Link_zero = getValueWithForm(dict, "SharedMedia.Link", .zero) - self._SharedMedia_Link_one = getValueWithForm(dict, "SharedMedia.Link", .one) - self._SharedMedia_Link_two = getValueWithForm(dict, "SharedMedia.Link", .two) - self._SharedMedia_Link_few = getValueWithForm(dict, "SharedMedia.Link", .few) - self._SharedMedia_Link_many = getValueWithForm(dict, "SharedMedia.Link", .many) - self._SharedMedia_Link_other = getValueWithForm(dict, "SharedMedia.Link", .other) - self._Media_ShareItem_zero = getValueWithForm(dict, "Media.ShareItem", .zero) - self._Media_ShareItem_one = getValueWithForm(dict, "Media.ShareItem", .one) - self._Media_ShareItem_two = getValueWithForm(dict, "Media.ShareItem", .two) - self._Media_ShareItem_few = getValueWithForm(dict, "Media.ShareItem", .few) - self._Media_ShareItem_many = getValueWithForm(dict, "Media.ShareItem", .many) - self._Media_ShareItem_other = getValueWithForm(dict, "Media.ShareItem", .other) - self._PasscodeSettings_FailedAttempts_zero = getValueWithForm(dict, "PasscodeSettings.FailedAttempts", .zero) - self._PasscodeSettings_FailedAttempts_one = getValueWithForm(dict, "PasscodeSettings.FailedAttempts", .one) - self._PasscodeSettings_FailedAttempts_two = getValueWithForm(dict, "PasscodeSettings.FailedAttempts", .two) - self._PasscodeSettings_FailedAttempts_few = getValueWithForm(dict, "PasscodeSettings.FailedAttempts", .few) - self._PasscodeSettings_FailedAttempts_many = getValueWithForm(dict, "PasscodeSettings.FailedAttempts", .many) - self._PasscodeSettings_FailedAttempts_other = getValueWithForm(dict, "PasscodeSettings.FailedAttempts", .other) - self._Notification_GameScoreSelfSimple_zero = getValueWithForm(dict, "Notification.GameScoreSelfSimple", .zero) - self._Notification_GameScoreSelfSimple_one = getValueWithForm(dict, "Notification.GameScoreSelfSimple", .one) - self._Notification_GameScoreSelfSimple_two = getValueWithForm(dict, "Notification.GameScoreSelfSimple", .two) - self._Notification_GameScoreSelfSimple_few = getValueWithForm(dict, "Notification.GameScoreSelfSimple", .few) - self._Notification_GameScoreSelfSimple_many = getValueWithForm(dict, "Notification.GameScoreSelfSimple", .many) - self._Notification_GameScoreSelfSimple_other = getValueWithForm(dict, "Notification.GameScoreSelfSimple", .other) - self._Notifications_ExceptionMuteExpires_Hours_zero = getValueWithForm(dict, "Notifications.ExceptionMuteExpires.Hours", .zero) - self._Notifications_ExceptionMuteExpires_Hours_one = getValueWithForm(dict, "Notifications.ExceptionMuteExpires.Hours", .one) - self._Notifications_ExceptionMuteExpires_Hours_two = getValueWithForm(dict, "Notifications.ExceptionMuteExpires.Hours", .two) - self._Notifications_ExceptionMuteExpires_Hours_few = getValueWithForm(dict, "Notifications.ExceptionMuteExpires.Hours", .few) - self._Notifications_ExceptionMuteExpires_Hours_many = getValueWithForm(dict, "Notifications.ExceptionMuteExpires.Hours", .many) - self._Notifications_ExceptionMuteExpires_Hours_other = getValueWithForm(dict, "Notifications.ExceptionMuteExpires.Hours", .other) - self._LastSeen_HoursAgo_zero = getValueWithForm(dict, "LastSeen.HoursAgo", .zero) - self._LastSeen_HoursAgo_one = getValueWithForm(dict, "LastSeen.HoursAgo", .one) - self._LastSeen_HoursAgo_two = getValueWithForm(dict, "LastSeen.HoursAgo", .two) - self._LastSeen_HoursAgo_few = getValueWithForm(dict, "LastSeen.HoursAgo", .few) - self._LastSeen_HoursAgo_many = getValueWithForm(dict, "LastSeen.HoursAgo", .many) - self._LastSeen_HoursAgo_other = getValueWithForm(dict, "LastSeen.HoursAgo", .other) - self._MessageTimer_Seconds_zero = getValueWithForm(dict, "MessageTimer.Seconds", .zero) - self._MessageTimer_Seconds_one = getValueWithForm(dict, "MessageTimer.Seconds", .one) - self._MessageTimer_Seconds_two = getValueWithForm(dict, "MessageTimer.Seconds", .two) - self._MessageTimer_Seconds_few = getValueWithForm(dict, "MessageTimer.Seconds", .few) - self._MessageTimer_Seconds_many = getValueWithForm(dict, "MessageTimer.Seconds", .many) - self._MessageTimer_Seconds_other = getValueWithForm(dict, "MessageTimer.Seconds", .other) - self._Notification_GameScoreSelfExtended_zero = getValueWithForm(dict, "Notification.GameScoreSelfExtended", .zero) - self._Notification_GameScoreSelfExtended_one = getValueWithForm(dict, "Notification.GameScoreSelfExtended", .one) - self._Notification_GameScoreSelfExtended_two = getValueWithForm(dict, "Notification.GameScoreSelfExtended", .two) - self._Notification_GameScoreSelfExtended_few = getValueWithForm(dict, "Notification.GameScoreSelfExtended", .few) - self._Notification_GameScoreSelfExtended_many = getValueWithForm(dict, "Notification.GameScoreSelfExtended", .many) - self._Notification_GameScoreSelfExtended_other = getValueWithForm(dict, "Notification.GameScoreSelfExtended", .other) - self._Notification_GameScoreSimple_zero = getValueWithForm(dict, "Notification.GameScoreSimple", .zero) - self._Notification_GameScoreSimple_one = getValueWithForm(dict, "Notification.GameScoreSimple", .one) - self._Notification_GameScoreSimple_two = getValueWithForm(dict, "Notification.GameScoreSimple", .two) - self._Notification_GameScoreSimple_few = getValueWithForm(dict, "Notification.GameScoreSimple", .few) - self._Notification_GameScoreSimple_many = getValueWithForm(dict, "Notification.GameScoreSimple", .many) - self._Notification_GameScoreSimple_other = getValueWithForm(dict, "Notification.GameScoreSimple", .other) - self._MuteFor_Days_zero = getValueWithForm(dict, "MuteFor.Days", .zero) - self._MuteFor_Days_one = getValueWithForm(dict, "MuteFor.Days", .one) - self._MuteFor_Days_two = getValueWithForm(dict, "MuteFor.Days", .two) - self._MuteFor_Days_few = getValueWithForm(dict, "MuteFor.Days", .few) - self._MuteFor_Days_many = getValueWithForm(dict, "MuteFor.Days", .many) - self._MuteFor_Days_other = getValueWithForm(dict, "MuteFor.Days", .other) - self._Conversation_StatusSubscribers_zero = getValueWithForm(dict, "Conversation.StatusSubscribers", .zero) - self._Conversation_StatusSubscribers_one = getValueWithForm(dict, "Conversation.StatusSubscribers", .one) - self._Conversation_StatusSubscribers_two = getValueWithForm(dict, "Conversation.StatusSubscribers", .two) - self._Conversation_StatusSubscribers_few = getValueWithForm(dict, "Conversation.StatusSubscribers", .few) - self._Conversation_StatusSubscribers_many = getValueWithForm(dict, "Conversation.StatusSubscribers", .many) - self._Conversation_StatusSubscribers_other = getValueWithForm(dict, "Conversation.StatusSubscribers", .other) - self._ForwardedGifs_zero = getValueWithForm(dict, "ForwardedGifs", .zero) - self._ForwardedGifs_one = getValueWithForm(dict, "ForwardedGifs", .one) - self._ForwardedGifs_two = getValueWithForm(dict, "ForwardedGifs", .two) - self._ForwardedGifs_few = getValueWithForm(dict, "ForwardedGifs", .few) - self._ForwardedGifs_many = getValueWithForm(dict, "ForwardedGifs", .many) - self._ForwardedGifs_other = getValueWithForm(dict, "ForwardedGifs", .other) - self._StickerPack_AddMaskCount_zero = getValueWithForm(dict, "StickerPack.AddMaskCount", .zero) - self._StickerPack_AddMaskCount_one = getValueWithForm(dict, "StickerPack.AddMaskCount", .one) - self._StickerPack_AddMaskCount_two = getValueWithForm(dict, "StickerPack.AddMaskCount", .two) - self._StickerPack_AddMaskCount_few = getValueWithForm(dict, "StickerPack.AddMaskCount", .few) - self._StickerPack_AddMaskCount_many = getValueWithForm(dict, "StickerPack.AddMaskCount", .many) - self._StickerPack_AddMaskCount_other = getValueWithForm(dict, "StickerPack.AddMaskCount", .other) - self._MessageTimer_ShortSeconds_zero = getValueWithForm(dict, "MessageTimer.ShortSeconds", .zero) - self._MessageTimer_ShortSeconds_one = getValueWithForm(dict, "MessageTimer.ShortSeconds", .one) - self._MessageTimer_ShortSeconds_two = getValueWithForm(dict, "MessageTimer.ShortSeconds", .two) - self._MessageTimer_ShortSeconds_few = getValueWithForm(dict, "MessageTimer.ShortSeconds", .few) - self._MessageTimer_ShortSeconds_many = getValueWithForm(dict, "MessageTimer.ShortSeconds", .many) - self._MessageTimer_ShortSeconds_other = getValueWithForm(dict, "MessageTimer.ShortSeconds", .other) - self._AttachmentMenu_SendPhoto_zero = getValueWithForm(dict, "AttachmentMenu.SendPhoto", .zero) - self._AttachmentMenu_SendPhoto_one = getValueWithForm(dict, "AttachmentMenu.SendPhoto", .one) - self._AttachmentMenu_SendPhoto_two = getValueWithForm(dict, "AttachmentMenu.SendPhoto", .two) - self._AttachmentMenu_SendPhoto_few = getValueWithForm(dict, "AttachmentMenu.SendPhoto", .few) - self._AttachmentMenu_SendPhoto_many = getValueWithForm(dict, "AttachmentMenu.SendPhoto", .many) - self._AttachmentMenu_SendPhoto_other = getValueWithForm(dict, "AttachmentMenu.SendPhoto", .other) - self._Notifications_ExceptionMuteExpires_Minutes_zero = getValueWithForm(dict, "Notifications.ExceptionMuteExpires.Minutes", .zero) - self._Notifications_ExceptionMuteExpires_Minutes_one = getValueWithForm(dict, "Notifications.ExceptionMuteExpires.Minutes", .one) - self._Notifications_ExceptionMuteExpires_Minutes_two = getValueWithForm(dict, "Notifications.ExceptionMuteExpires.Minutes", .two) - self._Notifications_ExceptionMuteExpires_Minutes_few = getValueWithForm(dict, "Notifications.ExceptionMuteExpires.Minutes", .few) - self._Notifications_ExceptionMuteExpires_Minutes_many = getValueWithForm(dict, "Notifications.ExceptionMuteExpires.Minutes", .many) - self._Notifications_ExceptionMuteExpires_Minutes_other = getValueWithForm(dict, "Notifications.ExceptionMuteExpires.Minutes", .other) - self._MessageTimer_ShortWeeks_zero = getValueWithForm(dict, "MessageTimer.ShortWeeks", .zero) - self._MessageTimer_ShortWeeks_one = getValueWithForm(dict, "MessageTimer.ShortWeeks", .one) - self._MessageTimer_ShortWeeks_two = getValueWithForm(dict, "MessageTimer.ShortWeeks", .two) - self._MessageTimer_ShortWeeks_few = getValueWithForm(dict, "MessageTimer.ShortWeeks", .few) - self._MessageTimer_ShortWeeks_many = getValueWithForm(dict, "MessageTimer.ShortWeeks", .many) - self._MessageTimer_ShortWeeks_other = getValueWithForm(dict, "MessageTimer.ShortWeeks", .other) - self._ForwardedStickers_zero = getValueWithForm(dict, "ForwardedStickers", .zero) - self._ForwardedStickers_one = getValueWithForm(dict, "ForwardedStickers", .one) - self._ForwardedStickers_two = getValueWithForm(dict, "ForwardedStickers", .two) - self._ForwardedStickers_few = getValueWithForm(dict, "ForwardedStickers", .few) - self._ForwardedStickers_many = getValueWithForm(dict, "ForwardedStickers", .many) - self._ForwardedStickers_other = getValueWithForm(dict, "ForwardedStickers", .other) - self._Forward_ConfirmMultipleFiles_zero = getValueWithForm(dict, "Forward.ConfirmMultipleFiles", .zero) - self._Forward_ConfirmMultipleFiles_one = getValueWithForm(dict, "Forward.ConfirmMultipleFiles", .one) - self._Forward_ConfirmMultipleFiles_two = getValueWithForm(dict, "Forward.ConfirmMultipleFiles", .two) - self._Forward_ConfirmMultipleFiles_few = getValueWithForm(dict, "Forward.ConfirmMultipleFiles", .few) - self._Forward_ConfirmMultipleFiles_many = getValueWithForm(dict, "Forward.ConfirmMultipleFiles", .many) - self._Forward_ConfirmMultipleFiles_other = getValueWithForm(dict, "Forward.ConfirmMultipleFiles", .other) - self._SharedMedia_Photo_zero = getValueWithForm(dict, "SharedMedia.Photo", .zero) - self._SharedMedia_Photo_one = getValueWithForm(dict, "SharedMedia.Photo", .one) - self._SharedMedia_Photo_two = getValueWithForm(dict, "SharedMedia.Photo", .two) - self._SharedMedia_Photo_few = getValueWithForm(dict, "SharedMedia.Photo", .few) - self._SharedMedia_Photo_many = getValueWithForm(dict, "SharedMedia.Photo", .many) - self._SharedMedia_Photo_other = getValueWithForm(dict, "SharedMedia.Photo", .other) - self._StickerPack_RemoveStickerCount_zero = getValueWithForm(dict, "StickerPack.RemoveStickerCount", .zero) - self._StickerPack_RemoveStickerCount_one = getValueWithForm(dict, "StickerPack.RemoveStickerCount", .one) - self._StickerPack_RemoveStickerCount_two = getValueWithForm(dict, "StickerPack.RemoveStickerCount", .two) - self._StickerPack_RemoveStickerCount_few = getValueWithForm(dict, "StickerPack.RemoveStickerCount", .few) - self._StickerPack_RemoveStickerCount_many = getValueWithForm(dict, "StickerPack.RemoveStickerCount", .many) - self._StickerPack_RemoveStickerCount_other = getValueWithForm(dict, "StickerPack.RemoveStickerCount", .other) - self._InviteText_ContactsCountText_zero = getValueWithForm(dict, "InviteText.ContactsCountText", .zero) - self._InviteText_ContactsCountText_one = getValueWithForm(dict, "InviteText.ContactsCountText", .one) - self._InviteText_ContactsCountText_two = getValueWithForm(dict, "InviteText.ContactsCountText", .two) - self._InviteText_ContactsCountText_few = getValueWithForm(dict, "InviteText.ContactsCountText", .few) - self._InviteText_ContactsCountText_many = getValueWithForm(dict, "InviteText.ContactsCountText", .many) - self._InviteText_ContactsCountText_other = getValueWithForm(dict, "InviteText.ContactsCountText", .other) - self._MuteExpires_Hours_zero = getValueWithForm(dict, "MuteExpires.Hours", .zero) - self._MuteExpires_Hours_one = getValueWithForm(dict, "MuteExpires.Hours", .one) - self._MuteExpires_Hours_two = getValueWithForm(dict, "MuteExpires.Hours", .two) - self._MuteExpires_Hours_few = getValueWithForm(dict, "MuteExpires.Hours", .few) - self._MuteExpires_Hours_many = getValueWithForm(dict, "MuteExpires.Hours", .many) - self._MuteExpires_Hours_other = getValueWithForm(dict, "MuteExpires.Hours", .other) - self._Notification_GameScoreExtended_zero = getValueWithForm(dict, "Notification.GameScoreExtended", .zero) - self._Notification_GameScoreExtended_one = getValueWithForm(dict, "Notification.GameScoreExtended", .one) - self._Notification_GameScoreExtended_two = getValueWithForm(dict, "Notification.GameScoreExtended", .two) - self._Notification_GameScoreExtended_few = getValueWithForm(dict, "Notification.GameScoreExtended", .few) - self._Notification_GameScoreExtended_many = getValueWithForm(dict, "Notification.GameScoreExtended", .many) - self._Notification_GameScoreExtended_other = getValueWithForm(dict, "Notification.GameScoreExtended", .other) - self._MuteExpires_Minutes_zero = getValueWithForm(dict, "MuteExpires.Minutes", .zero) - self._MuteExpires_Minutes_one = getValueWithForm(dict, "MuteExpires.Minutes", .one) - self._MuteExpires_Minutes_two = getValueWithForm(dict, "MuteExpires.Minutes", .two) - self._MuteExpires_Minutes_few = getValueWithForm(dict, "MuteExpires.Minutes", .few) - self._MuteExpires_Minutes_many = getValueWithForm(dict, "MuteExpires.Minutes", .many) - self._MuteExpires_Minutes_other = getValueWithForm(dict, "MuteExpires.Minutes", .other) - self._AttachmentMenu_SendItem_zero = getValueWithForm(dict, "AttachmentMenu.SendItem", .zero) - self._AttachmentMenu_SendItem_one = getValueWithForm(dict, "AttachmentMenu.SendItem", .one) - self._AttachmentMenu_SendItem_two = getValueWithForm(dict, "AttachmentMenu.SendItem", .two) - self._AttachmentMenu_SendItem_few = getValueWithForm(dict, "AttachmentMenu.SendItem", .few) - self._AttachmentMenu_SendItem_many = getValueWithForm(dict, "AttachmentMenu.SendItem", .many) - self._AttachmentMenu_SendItem_other = getValueWithForm(dict, "AttachmentMenu.SendItem", .other) - self._ForwardedAudios_zero = getValueWithForm(dict, "ForwardedAudios", .zero) - self._ForwardedAudios_one = getValueWithForm(dict, "ForwardedAudios", .one) - self._ForwardedAudios_two = getValueWithForm(dict, "ForwardedAudios", .two) - self._ForwardedAudios_few = getValueWithForm(dict, "ForwardedAudios", .few) - self._ForwardedAudios_many = getValueWithForm(dict, "ForwardedAudios", .many) - self._ForwardedAudios_other = getValueWithForm(dict, "ForwardedAudios", .other) - self._SharedMedia_File_zero = getValueWithForm(dict, "SharedMedia.File", .zero) - self._SharedMedia_File_one = getValueWithForm(dict, "SharedMedia.File", .one) - self._SharedMedia_File_two = getValueWithForm(dict, "SharedMedia.File", .two) - self._SharedMedia_File_few = getValueWithForm(dict, "SharedMedia.File", .few) - self._SharedMedia_File_many = getValueWithForm(dict, "SharedMedia.File", .many) - self._SharedMedia_File_other = getValueWithForm(dict, "SharedMedia.File", .other) - self._Call_ShortMinutes_zero = getValueWithForm(dict, "Call.ShortMinutes", .zero) - self._Call_ShortMinutes_one = getValueWithForm(dict, "Call.ShortMinutes", .one) - self._Call_ShortMinutes_two = getValueWithForm(dict, "Call.ShortMinutes", .two) - self._Call_ShortMinutes_few = getValueWithForm(dict, "Call.ShortMinutes", .few) - self._Call_ShortMinutes_many = getValueWithForm(dict, "Call.ShortMinutes", .many) - self._Call_ShortMinutes_other = getValueWithForm(dict, "Call.ShortMinutes", .other) - self._Call_Minutes_zero = getValueWithForm(dict, "Call.Minutes", .zero) - self._Call_Minutes_one = getValueWithForm(dict, "Call.Minutes", .one) - self._Call_Minutes_two = getValueWithForm(dict, "Call.Minutes", .two) - self._Call_Minutes_few = getValueWithForm(dict, "Call.Minutes", .few) - self._Call_Minutes_many = getValueWithForm(dict, "Call.Minutes", .many) - self._Call_Minutes_other = getValueWithForm(dict, "Call.Minutes", .other) - self._Call_ShortSeconds_zero = getValueWithForm(dict, "Call.ShortSeconds", .zero) - self._Call_ShortSeconds_one = getValueWithForm(dict, "Call.ShortSeconds", .one) - self._Call_ShortSeconds_two = getValueWithForm(dict, "Call.ShortSeconds", .two) - self._Call_ShortSeconds_few = getValueWithForm(dict, "Call.ShortSeconds", .few) - self._Call_ShortSeconds_many = getValueWithForm(dict, "Call.ShortSeconds", .many) - self._Call_ShortSeconds_other = getValueWithForm(dict, "Call.ShortSeconds", .other) - self._Invitation_Members_zero = getValueWithForm(dict, "Invitation.Members", .zero) - self._Invitation_Members_one = getValueWithForm(dict, "Invitation.Members", .one) - self._Invitation_Members_two = getValueWithForm(dict, "Invitation.Members", .two) - self._Invitation_Members_few = getValueWithForm(dict, "Invitation.Members", .few) - self._Invitation_Members_many = getValueWithForm(dict, "Invitation.Members", .many) - self._Invitation_Members_other = getValueWithForm(dict, "Invitation.Members", .other) - self._AttachmentMenu_SendGif_zero = getValueWithForm(dict, "AttachmentMenu.SendGif", .zero) - self._AttachmentMenu_SendGif_one = getValueWithForm(dict, "AttachmentMenu.SendGif", .one) - self._AttachmentMenu_SendGif_two = getValueWithForm(dict, "AttachmentMenu.SendGif", .two) - self._AttachmentMenu_SendGif_few = getValueWithForm(dict, "AttachmentMenu.SendGif", .few) - self._AttachmentMenu_SendGif_many = getValueWithForm(dict, "AttachmentMenu.SendGif", .many) - self._AttachmentMenu_SendGif_other = getValueWithForm(dict, "AttachmentMenu.SendGif", .other) - self._Conversation_LiveLocationMembersCount_zero = getValueWithForm(dict, "Conversation.LiveLocationMembersCount", .zero) - self._Conversation_LiveLocationMembersCount_one = getValueWithForm(dict, "Conversation.LiveLocationMembersCount", .one) - self._Conversation_LiveLocationMembersCount_two = getValueWithForm(dict, "Conversation.LiveLocationMembersCount", .two) - self._Conversation_LiveLocationMembersCount_few = getValueWithForm(dict, "Conversation.LiveLocationMembersCount", .few) - self._Conversation_LiveLocationMembersCount_many = getValueWithForm(dict, "Conversation.LiveLocationMembersCount", .many) - self._Conversation_LiveLocationMembersCount_other = getValueWithForm(dict, "Conversation.LiveLocationMembersCount", .other) - self._Watch_LastSeen_MinutesAgo_zero = getValueWithForm(dict, "Watch.LastSeen.MinutesAgo", .zero) - self._Watch_LastSeen_MinutesAgo_one = getValueWithForm(dict, "Watch.LastSeen.MinutesAgo", .one) - self._Watch_LastSeen_MinutesAgo_two = getValueWithForm(dict, "Watch.LastSeen.MinutesAgo", .two) - self._Watch_LastSeen_MinutesAgo_few = getValueWithForm(dict, "Watch.LastSeen.MinutesAgo", .few) - self._Watch_LastSeen_MinutesAgo_many = getValueWithForm(dict, "Watch.LastSeen.MinutesAgo", .many) - self._Watch_LastSeen_MinutesAgo_other = getValueWithForm(dict, "Watch.LastSeen.MinutesAgo", .other) - self._MessageTimer_Months_zero = getValueWithForm(dict, "MessageTimer.Months", .zero) - self._MessageTimer_Months_one = getValueWithForm(dict, "MessageTimer.Months", .one) - self._MessageTimer_Months_two = getValueWithForm(dict, "MessageTimer.Months", .two) - self._MessageTimer_Months_few = getValueWithForm(dict, "MessageTimer.Months", .few) - self._MessageTimer_Months_many = getValueWithForm(dict, "MessageTimer.Months", .many) - self._MessageTimer_Months_other = getValueWithForm(dict, "MessageTimer.Months", .other) - self._ForwardedMessages_zero = getValueWithForm(dict, "ForwardedMessages", .zero) - self._ForwardedMessages_one = getValueWithForm(dict, "ForwardedMessages", .one) - self._ForwardedMessages_two = getValueWithForm(dict, "ForwardedMessages", .two) - self._ForwardedMessages_few = getValueWithForm(dict, "ForwardedMessages", .few) - self._ForwardedMessages_many = getValueWithForm(dict, "ForwardedMessages", .many) - self._ForwardedMessages_other = getValueWithForm(dict, "ForwardedMessages", .other) - self._ForwardedVideos_zero = getValueWithForm(dict, "ForwardedVideos", .zero) - self._ForwardedVideos_one = getValueWithForm(dict, "ForwardedVideos", .one) - self._ForwardedVideos_two = getValueWithForm(dict, "ForwardedVideos", .two) - self._ForwardedVideos_few = getValueWithForm(dict, "ForwardedVideos", .few) - self._ForwardedVideos_many = getValueWithForm(dict, "ForwardedVideos", .many) - self._ForwardedVideos_other = getValueWithForm(dict, "ForwardedVideos", .other) - self._MessageTimer_Minutes_zero = getValueWithForm(dict, "MessageTimer.Minutes", .zero) - self._MessageTimer_Minutes_one = getValueWithForm(dict, "MessageTimer.Minutes", .one) - self._MessageTimer_Minutes_two = getValueWithForm(dict, "MessageTimer.Minutes", .two) - self._MessageTimer_Minutes_few = getValueWithForm(dict, "MessageTimer.Minutes", .few) - self._MessageTimer_Minutes_many = getValueWithForm(dict, "MessageTimer.Minutes", .many) - self._MessageTimer_Minutes_other = getValueWithForm(dict, "MessageTimer.Minutes", .other) self._Conversation_StatusOnline_zero = getValueWithForm(dict, "Conversation.StatusOnline", .zero) self._Conversation_StatusOnline_one = getValueWithForm(dict, "Conversation.StatusOnline", .one) self._Conversation_StatusOnline_two = getValueWithForm(dict, "Conversation.StatusOnline", .two) self._Conversation_StatusOnline_few = getValueWithForm(dict, "Conversation.StatusOnline", .few) self._Conversation_StatusOnline_many = getValueWithForm(dict, "Conversation.StatusOnline", .many) self._Conversation_StatusOnline_other = getValueWithForm(dict, "Conversation.StatusOnline", .other) - self._MessageTimer_ShortMinutes_zero = getValueWithForm(dict, "MessageTimer.ShortMinutes", .zero) - self._MessageTimer_ShortMinutes_one = getValueWithForm(dict, "MessageTimer.ShortMinutes", .one) - self._MessageTimer_ShortMinutes_two = getValueWithForm(dict, "MessageTimer.ShortMinutes", .two) - self._MessageTimer_ShortMinutes_few = getValueWithForm(dict, "MessageTimer.ShortMinutes", .few) - self._MessageTimer_ShortMinutes_many = getValueWithForm(dict, "MessageTimer.ShortMinutes", .many) - self._MessageTimer_ShortMinutes_other = getValueWithForm(dict, "MessageTimer.ShortMinutes", .other) - self._QuickSend_Photos_zero = getValueWithForm(dict, "QuickSend.Photos", .zero) - self._QuickSend_Photos_one = getValueWithForm(dict, "QuickSend.Photos", .one) - self._QuickSend_Photos_two = getValueWithForm(dict, "QuickSend.Photos", .two) - self._QuickSend_Photos_few = getValueWithForm(dict, "QuickSend.Photos", .few) - self._QuickSend_Photos_many = getValueWithForm(dict, "QuickSend.Photos", .many) - self._QuickSend_Photos_other = getValueWithForm(dict, "QuickSend.Photos", .other) - self._ServiceMessage_GameScoreExtended_zero = getValueWithForm(dict, "ServiceMessage.GameScoreExtended", .zero) - self._ServiceMessage_GameScoreExtended_one = getValueWithForm(dict, "ServiceMessage.GameScoreExtended", .one) - self._ServiceMessage_GameScoreExtended_two = getValueWithForm(dict, "ServiceMessage.GameScoreExtended", .two) - self._ServiceMessage_GameScoreExtended_few = getValueWithForm(dict, "ServiceMessage.GameScoreExtended", .few) - self._ServiceMessage_GameScoreExtended_many = getValueWithForm(dict, "ServiceMessage.GameScoreExtended", .many) - self._ServiceMessage_GameScoreExtended_other = getValueWithForm(dict, "ServiceMessage.GameScoreExtended", .other) - self._SharedMedia_Generic_zero = getValueWithForm(dict, "SharedMedia.Generic", .zero) - self._SharedMedia_Generic_one = getValueWithForm(dict, "SharedMedia.Generic", .one) - self._SharedMedia_Generic_two = getValueWithForm(dict, "SharedMedia.Generic", .two) - self._SharedMedia_Generic_few = getValueWithForm(dict, "SharedMedia.Generic", .few) - self._SharedMedia_Generic_many = getValueWithForm(dict, "SharedMedia.Generic", .many) - self._SharedMedia_Generic_other = getValueWithForm(dict, "SharedMedia.Generic", .other) - self._SharedMedia_Video_zero = getValueWithForm(dict, "SharedMedia.Video", .zero) - self._SharedMedia_Video_one = getValueWithForm(dict, "SharedMedia.Video", .one) - self._SharedMedia_Video_two = getValueWithForm(dict, "SharedMedia.Video", .two) - self._SharedMedia_Video_few = getValueWithForm(dict, "SharedMedia.Video", .few) - self._SharedMedia_Video_many = getValueWithForm(dict, "SharedMedia.Video", .many) - self._SharedMedia_Video_other = getValueWithForm(dict, "SharedMedia.Video", .other) - self._ServiceMessage_GameScoreSelfSimple_zero = getValueWithForm(dict, "ServiceMessage.GameScoreSelfSimple", .zero) - self._ServiceMessage_GameScoreSelfSimple_one = getValueWithForm(dict, "ServiceMessage.GameScoreSelfSimple", .one) - self._ServiceMessage_GameScoreSelfSimple_two = getValueWithForm(dict, "ServiceMessage.GameScoreSelfSimple", .two) - self._ServiceMessage_GameScoreSelfSimple_few = getValueWithForm(dict, "ServiceMessage.GameScoreSelfSimple", .few) - self._ServiceMessage_GameScoreSelfSimple_many = getValueWithForm(dict, "ServiceMessage.GameScoreSelfSimple", .many) - self._ServiceMessage_GameScoreSelfSimple_other = getValueWithForm(dict, "ServiceMessage.GameScoreSelfSimple", .other) - self._Media_ShareVideo_zero = getValueWithForm(dict, "Media.ShareVideo", .zero) - self._Media_ShareVideo_one = getValueWithForm(dict, "Media.ShareVideo", .one) - self._Media_ShareVideo_two = getValueWithForm(dict, "Media.ShareVideo", .two) - self._Media_ShareVideo_few = getValueWithForm(dict, "Media.ShareVideo", .few) - self._Media_ShareVideo_many = getValueWithForm(dict, "Media.ShareVideo", .many) - self._Media_ShareVideo_other = getValueWithForm(dict, "Media.ShareVideo", .other) - self._PrivacyLastSeenSettings_AddUsers_zero = getValueWithForm(dict, "PrivacyLastSeenSettings.AddUsers", .zero) - self._PrivacyLastSeenSettings_AddUsers_one = getValueWithForm(dict, "PrivacyLastSeenSettings.AddUsers", .one) - self._PrivacyLastSeenSettings_AddUsers_two = getValueWithForm(dict, "PrivacyLastSeenSettings.AddUsers", .two) - self._PrivacyLastSeenSettings_AddUsers_few = getValueWithForm(dict, "PrivacyLastSeenSettings.AddUsers", .few) - self._PrivacyLastSeenSettings_AddUsers_many = getValueWithForm(dict, "PrivacyLastSeenSettings.AddUsers", .many) - self._PrivacyLastSeenSettings_AddUsers_other = getValueWithForm(dict, "PrivacyLastSeenSettings.AddUsers", .other) - self._ForwardedContacts_zero = getValueWithForm(dict, "ForwardedContacts", .zero) - self._ForwardedContacts_one = getValueWithForm(dict, "ForwardedContacts", .one) - self._ForwardedContacts_two = getValueWithForm(dict, "ForwardedContacts", .two) - self._ForwardedContacts_few = getValueWithForm(dict, "ForwardedContacts", .few) - self._ForwardedContacts_many = getValueWithForm(dict, "ForwardedContacts", .many) - self._ForwardedContacts_other = getValueWithForm(dict, "ForwardedContacts", .other) - self._Call_Seconds_zero = getValueWithForm(dict, "Call.Seconds", .zero) - self._Call_Seconds_one = getValueWithForm(dict, "Call.Seconds", .one) - self._Call_Seconds_two = getValueWithForm(dict, "Call.Seconds", .two) - self._Call_Seconds_few = getValueWithForm(dict, "Call.Seconds", .few) - self._Call_Seconds_many = getValueWithForm(dict, "Call.Seconds", .many) - self._Call_Seconds_other = getValueWithForm(dict, "Call.Seconds", .other) - self._ForwardedAuthorsOthers_zero = getValueWithForm(dict, "ForwardedAuthorsOthers", .zero) - self._ForwardedAuthorsOthers_one = getValueWithForm(dict, "ForwardedAuthorsOthers", .one) - self._ForwardedAuthorsOthers_two = getValueWithForm(dict, "ForwardedAuthorsOthers", .two) - self._ForwardedAuthorsOthers_few = getValueWithForm(dict, "ForwardedAuthorsOthers", .few) - self._ForwardedAuthorsOthers_many = getValueWithForm(dict, "ForwardedAuthorsOthers", .many) - self._ForwardedAuthorsOthers_other = getValueWithForm(dict, "ForwardedAuthorsOthers", .other) - self._DialogList_LiveLocationChatsCount_zero = getValueWithForm(dict, "DialogList.LiveLocationChatsCount", .zero) - self._DialogList_LiveLocationChatsCount_one = getValueWithForm(dict, "DialogList.LiveLocationChatsCount", .one) - self._DialogList_LiveLocationChatsCount_two = getValueWithForm(dict, "DialogList.LiveLocationChatsCount", .two) - self._DialogList_LiveLocationChatsCount_few = getValueWithForm(dict, "DialogList.LiveLocationChatsCount", .few) - self._DialogList_LiveLocationChatsCount_many = getValueWithForm(dict, "DialogList.LiveLocationChatsCount", .many) - self._DialogList_LiveLocationChatsCount_other = getValueWithForm(dict, "DialogList.LiveLocationChatsCount", .other) - self._MessageTimer_Years_zero = getValueWithForm(dict, "MessageTimer.Years", .zero) - self._MessageTimer_Years_one = getValueWithForm(dict, "MessageTimer.Years", .one) - self._MessageTimer_Years_two = getValueWithForm(dict, "MessageTimer.Years", .two) - self._MessageTimer_Years_few = getValueWithForm(dict, "MessageTimer.Years", .few) - self._MessageTimer_Years_many = getValueWithForm(dict, "MessageTimer.Years", .many) - self._MessageTimer_Years_other = getValueWithForm(dict, "MessageTimer.Years", .other) - self._LiveLocationUpdated_MinutesAgo_zero = getValueWithForm(dict, "LiveLocationUpdated.MinutesAgo", .zero) - self._LiveLocationUpdated_MinutesAgo_one = getValueWithForm(dict, "LiveLocationUpdated.MinutesAgo", .one) - self._LiveLocationUpdated_MinutesAgo_two = getValueWithForm(dict, "LiveLocationUpdated.MinutesAgo", .two) - self._LiveLocationUpdated_MinutesAgo_few = getValueWithForm(dict, "LiveLocationUpdated.MinutesAgo", .few) - self._LiveLocationUpdated_MinutesAgo_many = getValueWithForm(dict, "LiveLocationUpdated.MinutesAgo", .many) - self._LiveLocationUpdated_MinutesAgo_other = getValueWithForm(dict, "LiveLocationUpdated.MinutesAgo", .other) - self._StickerPack_RemoveMaskCount_zero = getValueWithForm(dict, "StickerPack.RemoveMaskCount", .zero) - self._StickerPack_RemoveMaskCount_one = getValueWithForm(dict, "StickerPack.RemoveMaskCount", .one) - self._StickerPack_RemoveMaskCount_two = getValueWithForm(dict, "StickerPack.RemoveMaskCount", .two) - self._StickerPack_RemoveMaskCount_few = getValueWithForm(dict, "StickerPack.RemoveMaskCount", .few) - self._StickerPack_RemoveMaskCount_many = getValueWithForm(dict, "StickerPack.RemoveMaskCount", .many) - self._StickerPack_RemoveMaskCount_other = getValueWithForm(dict, "StickerPack.RemoveMaskCount", .other) - self._ForwardedVideoMessages_zero = getValueWithForm(dict, "ForwardedVideoMessages", .zero) - self._ForwardedVideoMessages_one = getValueWithForm(dict, "ForwardedVideoMessages", .one) - self._ForwardedVideoMessages_two = getValueWithForm(dict, "ForwardedVideoMessages", .two) - self._ForwardedVideoMessages_few = getValueWithForm(dict, "ForwardedVideoMessages", .few) - self._ForwardedVideoMessages_many = getValueWithForm(dict, "ForwardedVideoMessages", .many) - self._ForwardedVideoMessages_other = getValueWithForm(dict, "ForwardedVideoMessages", .other) - self._MessageTimer_ShortHours_zero = getValueWithForm(dict, "MessageTimer.ShortHours", .zero) - self._MessageTimer_ShortHours_one = getValueWithForm(dict, "MessageTimer.ShortHours", .one) - self._MessageTimer_ShortHours_two = getValueWithForm(dict, "MessageTimer.ShortHours", .two) - self._MessageTimer_ShortHours_few = getValueWithForm(dict, "MessageTimer.ShortHours", .few) - self._MessageTimer_ShortHours_many = getValueWithForm(dict, "MessageTimer.ShortHours", .many) - self._MessageTimer_ShortHours_other = getValueWithForm(dict, "MessageTimer.ShortHours", .other) - self._Watch_LastSeen_HoursAgo_zero = getValueWithForm(dict, "Watch.LastSeen.HoursAgo", .zero) - self._Watch_LastSeen_HoursAgo_one = getValueWithForm(dict, "Watch.LastSeen.HoursAgo", .one) - self._Watch_LastSeen_HoursAgo_two = getValueWithForm(dict, "Watch.LastSeen.HoursAgo", .two) - self._Watch_LastSeen_HoursAgo_few = getValueWithForm(dict, "Watch.LastSeen.HoursAgo", .few) - self._Watch_LastSeen_HoursAgo_many = getValueWithForm(dict, "Watch.LastSeen.HoursAgo", .many) - self._Watch_LastSeen_HoursAgo_other = getValueWithForm(dict, "Watch.LastSeen.HoursAgo", .other) - self._AttachmentMenu_SendVideo_zero = getValueWithForm(dict, "AttachmentMenu.SendVideo", .zero) - self._AttachmentMenu_SendVideo_one = getValueWithForm(dict, "AttachmentMenu.SendVideo", .one) - self._AttachmentMenu_SendVideo_two = getValueWithForm(dict, "AttachmentMenu.SendVideo", .two) - self._AttachmentMenu_SendVideo_few = getValueWithForm(dict, "AttachmentMenu.SendVideo", .few) - self._AttachmentMenu_SendVideo_many = getValueWithForm(dict, "AttachmentMenu.SendVideo", .many) - self._AttachmentMenu_SendVideo_other = getValueWithForm(dict, "AttachmentMenu.SendVideo", .other) - self._MessageTimer_Weeks_zero = getValueWithForm(dict, "MessageTimer.Weeks", .zero) - self._MessageTimer_Weeks_one = getValueWithForm(dict, "MessageTimer.Weeks", .one) - self._MessageTimer_Weeks_two = getValueWithForm(dict, "MessageTimer.Weeks", .two) - self._MessageTimer_Weeks_few = getValueWithForm(dict, "MessageTimer.Weeks", .few) - self._MessageTimer_Weeks_many = getValueWithForm(dict, "MessageTimer.Weeks", .many) - self._MessageTimer_Weeks_other = getValueWithForm(dict, "MessageTimer.Weeks", .other) - self._StickerPack_StickerCount_zero = getValueWithForm(dict, "StickerPack.StickerCount", .zero) - self._StickerPack_StickerCount_one = getValueWithForm(dict, "StickerPack.StickerCount", .one) - self._StickerPack_StickerCount_two = getValueWithForm(dict, "StickerPack.StickerCount", .two) - self._StickerPack_StickerCount_few = getValueWithForm(dict, "StickerPack.StickerCount", .few) - self._StickerPack_StickerCount_many = getValueWithForm(dict, "StickerPack.StickerCount", .many) - self._StickerPack_StickerCount_other = getValueWithForm(dict, "StickerPack.StickerCount", .other) - self._ServiceMessage_GameScoreSimple_zero = getValueWithForm(dict, "ServiceMessage.GameScoreSimple", .zero) - self._ServiceMessage_GameScoreSimple_one = getValueWithForm(dict, "ServiceMessage.GameScoreSimple", .one) - self._ServiceMessage_GameScoreSimple_two = getValueWithForm(dict, "ServiceMessage.GameScoreSimple", .two) - self._ServiceMessage_GameScoreSimple_few = getValueWithForm(dict, "ServiceMessage.GameScoreSimple", .few) - self._ServiceMessage_GameScoreSimple_many = getValueWithForm(dict, "ServiceMessage.GameScoreSimple", .many) - self._ServiceMessage_GameScoreSimple_other = getValueWithForm(dict, "ServiceMessage.GameScoreSimple", .other) - self._ForwardedFiles_zero = getValueWithForm(dict, "ForwardedFiles", .zero) - self._ForwardedFiles_one = getValueWithForm(dict, "ForwardedFiles", .one) - self._ForwardedFiles_two = getValueWithForm(dict, "ForwardedFiles", .two) - self._ForwardedFiles_few = getValueWithForm(dict, "ForwardedFiles", .few) - self._ForwardedFiles_many = getValueWithForm(dict, "ForwardedFiles", .many) - self._ForwardedFiles_other = getValueWithForm(dict, "ForwardedFiles", .other) + self._Call_Minutes_zero = getValueWithForm(dict, "Call.Minutes", .zero) + self._Call_Minutes_one = getValueWithForm(dict, "Call.Minutes", .one) + self._Call_Minutes_two = getValueWithForm(dict, "Call.Minutes", .two) + self._Call_Minutes_few = getValueWithForm(dict, "Call.Minutes", .few) + self._Call_Minutes_many = getValueWithForm(dict, "Call.Minutes", .many) + self._Call_Minutes_other = getValueWithForm(dict, "Call.Minutes", .other) self._GroupInfo_ParticipantCount_zero = getValueWithForm(dict, "GroupInfo.ParticipantCount", .zero) self._GroupInfo_ParticipantCount_one = getValueWithForm(dict, "GroupInfo.ParticipantCount", .one) self._GroupInfo_ParticipantCount_two = getValueWithForm(dict, "GroupInfo.ParticipantCount", .two) self._GroupInfo_ParticipantCount_few = getValueWithForm(dict, "GroupInfo.ParticipantCount", .few) self._GroupInfo_ParticipantCount_many = getValueWithForm(dict, "GroupInfo.ParticipantCount", .many) self._GroupInfo_ParticipantCount_other = getValueWithForm(dict, "GroupInfo.ParticipantCount", .other) - self._ForwardedLocations_zero = getValueWithForm(dict, "ForwardedLocations", .zero) - self._ForwardedLocations_one = getValueWithForm(dict, "ForwardedLocations", .one) - self._ForwardedLocations_two = getValueWithForm(dict, "ForwardedLocations", .two) - self._ForwardedLocations_few = getValueWithForm(dict, "ForwardedLocations", .few) - self._ForwardedLocations_many = getValueWithForm(dict, "ForwardedLocations", .many) - self._ForwardedLocations_other = getValueWithForm(dict, "ForwardedLocations", .other) - self._SharedMedia_DeleteItemsConfirmation_zero = getValueWithForm(dict, "SharedMedia.DeleteItemsConfirmation", .zero) - self._SharedMedia_DeleteItemsConfirmation_one = getValueWithForm(dict, "SharedMedia.DeleteItemsConfirmation", .one) - self._SharedMedia_DeleteItemsConfirmation_two = getValueWithForm(dict, "SharedMedia.DeleteItemsConfirmation", .two) - self._SharedMedia_DeleteItemsConfirmation_few = getValueWithForm(dict, "SharedMedia.DeleteItemsConfirmation", .few) - self._SharedMedia_DeleteItemsConfirmation_many = getValueWithForm(dict, "SharedMedia.DeleteItemsConfirmation", .many) - self._SharedMedia_DeleteItemsConfirmation_other = getValueWithForm(dict, "SharedMedia.DeleteItemsConfirmation", .other) - self._ForwardedPhotos_zero = getValueWithForm(dict, "ForwardedPhotos", .zero) - self._ForwardedPhotos_one = getValueWithForm(dict, "ForwardedPhotos", .one) - self._ForwardedPhotos_two = getValueWithForm(dict, "ForwardedPhotos", .two) - self._ForwardedPhotos_few = getValueWithForm(dict, "ForwardedPhotos", .few) - self._ForwardedPhotos_many = getValueWithForm(dict, "ForwardedPhotos", .many) - self._ForwardedPhotos_other = getValueWithForm(dict, "ForwardedPhotos", .other) - self._MessageTimer_ShortDays_zero = getValueWithForm(dict, "MessageTimer.ShortDays", .zero) - self._MessageTimer_ShortDays_one = getValueWithForm(dict, "MessageTimer.ShortDays", .one) - self._MessageTimer_ShortDays_two = getValueWithForm(dict, "MessageTimer.ShortDays", .two) - self._MessageTimer_ShortDays_few = getValueWithForm(dict, "MessageTimer.ShortDays", .few) - self._MessageTimer_ShortDays_many = getValueWithForm(dict, "MessageTimer.ShortDays", .many) - self._MessageTimer_ShortDays_other = getValueWithForm(dict, "MessageTimer.ShortDays", .other) + self._MessageTimer_Weeks_zero = getValueWithForm(dict, "MessageTimer.Weeks", .zero) + self._MessageTimer_Weeks_one = getValueWithForm(dict, "MessageTimer.Weeks", .one) + self._MessageTimer_Weeks_two = getValueWithForm(dict, "MessageTimer.Weeks", .two) + self._MessageTimer_Weeks_few = getValueWithForm(dict, "MessageTimer.Weeks", .few) + self._MessageTimer_Weeks_many = getValueWithForm(dict, "MessageTimer.Weeks", .many) + self._MessageTimer_Weeks_other = getValueWithForm(dict, "MessageTimer.Weeks", .other) + self._ForwardedAudios_zero = getValueWithForm(dict, "ForwardedAudios", .zero) + self._ForwardedAudios_one = getValueWithForm(dict, "ForwardedAudios", .one) + self._ForwardedAudios_two = getValueWithForm(dict, "ForwardedAudios", .two) + self._ForwardedAudios_few = getValueWithForm(dict, "ForwardedAudios", .few) + self._ForwardedAudios_many = getValueWithForm(dict, "ForwardedAudios", .many) + self._ForwardedAudios_other = getValueWithForm(dict, "ForwardedAudios", .other) + self._Notification_GameScoreSimple_zero = getValueWithForm(dict, "Notification.GameScoreSimple", .zero) + self._Notification_GameScoreSimple_one = getValueWithForm(dict, "Notification.GameScoreSimple", .one) + self._Notification_GameScoreSimple_two = getValueWithForm(dict, "Notification.GameScoreSimple", .two) + self._Notification_GameScoreSimple_few = getValueWithForm(dict, "Notification.GameScoreSimple", .few) + self._Notification_GameScoreSimple_many = getValueWithForm(dict, "Notification.GameScoreSimple", .many) + self._Notification_GameScoreSimple_other = getValueWithForm(dict, "Notification.GameScoreSimple", .other) + self._AttachmentMenu_SendGif_zero = getValueWithForm(dict, "AttachmentMenu.SendGif", .zero) + self._AttachmentMenu_SendGif_one = getValueWithForm(dict, "AttachmentMenu.SendGif", .one) + self._AttachmentMenu_SendGif_two = getValueWithForm(dict, "AttachmentMenu.SendGif", .two) + self._AttachmentMenu_SendGif_few = getValueWithForm(dict, "AttachmentMenu.SendGif", .few) + self._AttachmentMenu_SendGif_many = getValueWithForm(dict, "AttachmentMenu.SendGif", .many) + self._AttachmentMenu_SendGif_other = getValueWithForm(dict, "AttachmentMenu.SendGif", .other) + self._Invitation_Members_zero = getValueWithForm(dict, "Invitation.Members", .zero) + self._Invitation_Members_one = getValueWithForm(dict, "Invitation.Members", .one) + self._Invitation_Members_two = getValueWithForm(dict, "Invitation.Members", .two) + self._Invitation_Members_few = getValueWithForm(dict, "Invitation.Members", .few) + self._Invitation_Members_many = getValueWithForm(dict, "Invitation.Members", .many) + self._Invitation_Members_other = getValueWithForm(dict, "Invitation.Members", .other) + self._AttachmentMenu_SendItem_zero = getValueWithForm(dict, "AttachmentMenu.SendItem", .zero) + self._AttachmentMenu_SendItem_one = getValueWithForm(dict, "AttachmentMenu.SendItem", .one) + self._AttachmentMenu_SendItem_two = getValueWithForm(dict, "AttachmentMenu.SendItem", .two) + self._AttachmentMenu_SendItem_few = getValueWithForm(dict, "AttachmentMenu.SendItem", .few) + self._AttachmentMenu_SendItem_many = getValueWithForm(dict, "AttachmentMenu.SendItem", .many) + self._AttachmentMenu_SendItem_other = getValueWithForm(dict, "AttachmentMenu.SendItem", .other) + self._MessageTimer_ShortHours_zero = getValueWithForm(dict, "MessageTimer.ShortHours", .zero) + self._MessageTimer_ShortHours_one = getValueWithForm(dict, "MessageTimer.ShortHours", .one) + self._MessageTimer_ShortHours_two = getValueWithForm(dict, "MessageTimer.ShortHours", .two) + self._MessageTimer_ShortHours_few = getValueWithForm(dict, "MessageTimer.ShortHours", .few) + self._MessageTimer_ShortHours_many = getValueWithForm(dict, "MessageTimer.ShortHours", .many) + self._MessageTimer_ShortHours_other = getValueWithForm(dict, "MessageTimer.ShortHours", .other) + self._ForwardedFiles_zero = getValueWithForm(dict, "ForwardedFiles", .zero) + self._ForwardedFiles_one = getValueWithForm(dict, "ForwardedFiles", .one) + self._ForwardedFiles_two = getValueWithForm(dict, "ForwardedFiles", .two) + self._ForwardedFiles_few = getValueWithForm(dict, "ForwardedFiles", .few) + self._ForwardedFiles_many = getValueWithForm(dict, "ForwardedFiles", .many) + self._ForwardedFiles_other = getValueWithForm(dict, "ForwardedFiles", .other) + self._MuteExpires_Minutes_zero = getValueWithForm(dict, "MuteExpires.Minutes", .zero) + self._MuteExpires_Minutes_one = getValueWithForm(dict, "MuteExpires.Minutes", .one) + self._MuteExpires_Minutes_two = getValueWithForm(dict, "MuteExpires.Minutes", .two) + self._MuteExpires_Minutes_few = getValueWithForm(dict, "MuteExpires.Minutes", .few) + self._MuteExpires_Minutes_many = getValueWithForm(dict, "MuteExpires.Minutes", .many) + self._MuteExpires_Minutes_other = getValueWithForm(dict, "MuteExpires.Minutes", .other) + self._ForwardedAuthorsOthers_zero = getValueWithForm(dict, "ForwardedAuthorsOthers", .zero) + self._ForwardedAuthorsOthers_one = getValueWithForm(dict, "ForwardedAuthorsOthers", .one) + self._ForwardedAuthorsOthers_two = getValueWithForm(dict, "ForwardedAuthorsOthers", .two) + self._ForwardedAuthorsOthers_few = getValueWithForm(dict, "ForwardedAuthorsOthers", .few) + self._ForwardedAuthorsOthers_many = getValueWithForm(dict, "ForwardedAuthorsOthers", .many) + self._ForwardedAuthorsOthers_other = getValueWithForm(dict, "ForwardedAuthorsOthers", .other) + self._Map_ETAHours_zero = getValueWithForm(dict, "Map.ETAHours", .zero) + self._Map_ETAHours_one = getValueWithForm(dict, "Map.ETAHours", .one) + self._Map_ETAHours_two = getValueWithForm(dict, "Map.ETAHours", .two) + self._Map_ETAHours_few = getValueWithForm(dict, "Map.ETAHours", .few) + self._Map_ETAHours_many = getValueWithForm(dict, "Map.ETAHours", .many) + self._Map_ETAHours_other = getValueWithForm(dict, "Map.ETAHours", .other) + self._ForwardedMessages_zero = getValueWithForm(dict, "ForwardedMessages", .zero) + self._ForwardedMessages_one = getValueWithForm(dict, "ForwardedMessages", .one) + self._ForwardedMessages_two = getValueWithForm(dict, "ForwardedMessages", .two) + self._ForwardedMessages_few = getValueWithForm(dict, "ForwardedMessages", .few) + self._ForwardedMessages_many = getValueWithForm(dict, "ForwardedMessages", .many) + self._ForwardedMessages_other = getValueWithForm(dict, "ForwardedMessages", .other) + self._Notification_GameScoreSelfSimple_zero = getValueWithForm(dict, "Notification.GameScoreSelfSimple", .zero) + self._Notification_GameScoreSelfSimple_one = getValueWithForm(dict, "Notification.GameScoreSelfSimple", .one) + self._Notification_GameScoreSelfSimple_two = getValueWithForm(dict, "Notification.GameScoreSelfSimple", .two) + self._Notification_GameScoreSelfSimple_few = getValueWithForm(dict, "Notification.GameScoreSelfSimple", .few) + self._Notification_GameScoreSelfSimple_many = getValueWithForm(dict, "Notification.GameScoreSelfSimple", .many) + self._Notification_GameScoreSelfSimple_other = getValueWithForm(dict, "Notification.GameScoreSelfSimple", .other) + self._Contacts_ImportersCount_zero = getValueWithForm(dict, "Contacts.ImportersCount", .zero) + self._Contacts_ImportersCount_one = getValueWithForm(dict, "Contacts.ImportersCount", .one) + self._Contacts_ImportersCount_two = getValueWithForm(dict, "Contacts.ImportersCount", .two) + self._Contacts_ImportersCount_few = getValueWithForm(dict, "Contacts.ImportersCount", .few) + self._Contacts_ImportersCount_many = getValueWithForm(dict, "Contacts.ImportersCount", .many) + self._Contacts_ImportersCount_other = getValueWithForm(dict, "Contacts.ImportersCount", .other) + self._LastSeen_HoursAgo_zero = getValueWithForm(dict, "LastSeen.HoursAgo", .zero) + self._LastSeen_HoursAgo_one = getValueWithForm(dict, "LastSeen.HoursAgo", .one) + self._LastSeen_HoursAgo_two = getValueWithForm(dict, "LastSeen.HoursAgo", .two) + self._LastSeen_HoursAgo_few = getValueWithForm(dict, "LastSeen.HoursAgo", .few) + self._LastSeen_HoursAgo_many = getValueWithForm(dict, "LastSeen.HoursAgo", .many) + self._LastSeen_HoursAgo_other = getValueWithForm(dict, "LastSeen.HoursAgo", .other) + self._AttachmentMenu_SendPhoto_zero = getValueWithForm(dict, "AttachmentMenu.SendPhoto", .zero) + self._AttachmentMenu_SendPhoto_one = getValueWithForm(dict, "AttachmentMenu.SendPhoto", .one) + self._AttachmentMenu_SendPhoto_two = getValueWithForm(dict, "AttachmentMenu.SendPhoto", .two) + self._AttachmentMenu_SendPhoto_few = getValueWithForm(dict, "AttachmentMenu.SendPhoto", .few) + self._AttachmentMenu_SendPhoto_many = getValueWithForm(dict, "AttachmentMenu.SendPhoto", .many) + self._AttachmentMenu_SendPhoto_other = getValueWithForm(dict, "AttachmentMenu.SendPhoto", .other) + self._MuteFor_Hours_zero = getValueWithForm(dict, "MuteFor.Hours", .zero) + self._MuteFor_Hours_one = getValueWithForm(dict, "MuteFor.Hours", .one) + self._MuteFor_Hours_two = getValueWithForm(dict, "MuteFor.Hours", .two) + self._MuteFor_Hours_few = getValueWithForm(dict, "MuteFor.Hours", .few) + self._MuteFor_Hours_many = getValueWithForm(dict, "MuteFor.Hours", .many) + self._MuteFor_Hours_other = getValueWithForm(dict, "MuteFor.Hours", .other) + self._AttachmentMenu_SendVideo_zero = getValueWithForm(dict, "AttachmentMenu.SendVideo", .zero) + self._AttachmentMenu_SendVideo_one = getValueWithForm(dict, "AttachmentMenu.SendVideo", .one) + self._AttachmentMenu_SendVideo_two = getValueWithForm(dict, "AttachmentMenu.SendVideo", .two) + self._AttachmentMenu_SendVideo_few = getValueWithForm(dict, "AttachmentMenu.SendVideo", .few) + self._AttachmentMenu_SendVideo_many = getValueWithForm(dict, "AttachmentMenu.SendVideo", .many) + self._AttachmentMenu_SendVideo_other = getValueWithForm(dict, "AttachmentMenu.SendVideo", .other) + self._SharedMedia_Photo_zero = getValueWithForm(dict, "SharedMedia.Photo", .zero) + self._SharedMedia_Photo_one = getValueWithForm(dict, "SharedMedia.Photo", .one) + self._SharedMedia_Photo_two = getValueWithForm(dict, "SharedMedia.Photo", .two) + self._SharedMedia_Photo_few = getValueWithForm(dict, "SharedMedia.Photo", .few) + self._SharedMedia_Photo_many = getValueWithForm(dict, "SharedMedia.Photo", .many) + self._SharedMedia_Photo_other = getValueWithForm(dict, "SharedMedia.Photo", .other) + self._ForwardedGifs_zero = getValueWithForm(dict, "ForwardedGifs", .zero) + self._ForwardedGifs_one = getValueWithForm(dict, "ForwardedGifs", .one) + self._ForwardedGifs_two = getValueWithForm(dict, "ForwardedGifs", .two) + self._ForwardedGifs_few = getValueWithForm(dict, "ForwardedGifs", .few) + self._ForwardedGifs_many = getValueWithForm(dict, "ForwardedGifs", .many) + self._ForwardedGifs_other = getValueWithForm(dict, "ForwardedGifs", .other) + self._ServiceMessage_GameScoreExtended_zero = getValueWithForm(dict, "ServiceMessage.GameScoreExtended", .zero) + self._ServiceMessage_GameScoreExtended_one = getValueWithForm(dict, "ServiceMessage.GameScoreExtended", .one) + self._ServiceMessage_GameScoreExtended_two = getValueWithForm(dict, "ServiceMessage.GameScoreExtended", .two) + self._ServiceMessage_GameScoreExtended_few = getValueWithForm(dict, "ServiceMessage.GameScoreExtended", .few) + self._ServiceMessage_GameScoreExtended_many = getValueWithForm(dict, "ServiceMessage.GameScoreExtended", .many) + self._ServiceMessage_GameScoreExtended_other = getValueWithForm(dict, "ServiceMessage.GameScoreExtended", .other) + self._StickerPack_RemoveStickerCount_zero = getValueWithForm(dict, "StickerPack.RemoveStickerCount", .zero) + self._StickerPack_RemoveStickerCount_one = getValueWithForm(dict, "StickerPack.RemoveStickerCount", .one) + self._StickerPack_RemoveStickerCount_two = getValueWithForm(dict, "StickerPack.RemoveStickerCount", .two) + self._StickerPack_RemoveStickerCount_few = getValueWithForm(dict, "StickerPack.RemoveStickerCount", .few) + self._StickerPack_RemoveStickerCount_many = getValueWithForm(dict, "StickerPack.RemoveStickerCount", .many) + self._StickerPack_RemoveStickerCount_other = getValueWithForm(dict, "StickerPack.RemoveStickerCount", .other) + self._Media_ShareItem_zero = getValueWithForm(dict, "Media.ShareItem", .zero) + self._Media_ShareItem_one = getValueWithForm(dict, "Media.ShareItem", .one) + self._Media_ShareItem_two = getValueWithForm(dict, "Media.ShareItem", .two) + self._Media_ShareItem_few = getValueWithForm(dict, "Media.ShareItem", .few) + self._Media_ShareItem_many = getValueWithForm(dict, "Media.ShareItem", .many) + self._Media_ShareItem_other = getValueWithForm(dict, "Media.ShareItem", .other) + self._MuteFor_Days_zero = getValueWithForm(dict, "MuteFor.Days", .zero) + self._MuteFor_Days_one = getValueWithForm(dict, "MuteFor.Days", .one) + self._MuteFor_Days_two = getValueWithForm(dict, "MuteFor.Days", .two) + self._MuteFor_Days_few = getValueWithForm(dict, "MuteFor.Days", .few) + self._MuteFor_Days_many = getValueWithForm(dict, "MuteFor.Days", .many) + self._MuteFor_Days_other = getValueWithForm(dict, "MuteFor.Days", .other) + self._ForwardedContacts_zero = getValueWithForm(dict, "ForwardedContacts", .zero) + self._ForwardedContacts_one = getValueWithForm(dict, "ForwardedContacts", .one) + self._ForwardedContacts_two = getValueWithForm(dict, "ForwardedContacts", .two) + self._ForwardedContacts_few = getValueWithForm(dict, "ForwardedContacts", .few) + self._ForwardedContacts_many = getValueWithForm(dict, "ForwardedContacts", .many) + self._ForwardedContacts_other = getValueWithForm(dict, "ForwardedContacts", .other) + self._Notification_GameScoreExtended_zero = getValueWithForm(dict, "Notification.GameScoreExtended", .zero) + self._Notification_GameScoreExtended_one = getValueWithForm(dict, "Notification.GameScoreExtended", .one) + self._Notification_GameScoreExtended_two = getValueWithForm(dict, "Notification.GameScoreExtended", .two) + self._Notification_GameScoreExtended_few = getValueWithForm(dict, "Notification.GameScoreExtended", .few) + self._Notification_GameScoreExtended_many = getValueWithForm(dict, "Notification.GameScoreExtended", .many) + self._Notification_GameScoreExtended_other = getValueWithForm(dict, "Notification.GameScoreExtended", .other) + self._Call_Seconds_zero = getValueWithForm(dict, "Call.Seconds", .zero) + self._Call_Seconds_one = getValueWithForm(dict, "Call.Seconds", .one) + self._Call_Seconds_two = getValueWithForm(dict, "Call.Seconds", .two) + self._Call_Seconds_few = getValueWithForm(dict, "Call.Seconds", .few) + self._Call_Seconds_many = getValueWithForm(dict, "Call.Seconds", .many) + self._Call_Seconds_other = getValueWithForm(dict, "Call.Seconds", .other) + self._ForwardedStickers_zero = getValueWithForm(dict, "ForwardedStickers", .zero) + self._ForwardedStickers_one = getValueWithForm(dict, "ForwardedStickers", .one) + self._ForwardedStickers_two = getValueWithForm(dict, "ForwardedStickers", .two) + self._ForwardedStickers_few = getValueWithForm(dict, "ForwardedStickers", .few) + self._ForwardedStickers_many = getValueWithForm(dict, "ForwardedStickers", .many) + self._ForwardedStickers_other = getValueWithForm(dict, "ForwardedStickers", .other) + self._StickerPack_RemoveMaskCount_zero = getValueWithForm(dict, "StickerPack.RemoveMaskCount", .zero) + self._StickerPack_RemoveMaskCount_one = getValueWithForm(dict, "StickerPack.RemoveMaskCount", .one) + self._StickerPack_RemoveMaskCount_two = getValueWithForm(dict, "StickerPack.RemoveMaskCount", .two) + self._StickerPack_RemoveMaskCount_few = getValueWithForm(dict, "StickerPack.RemoveMaskCount", .few) + self._StickerPack_RemoveMaskCount_many = getValueWithForm(dict, "StickerPack.RemoveMaskCount", .many) + self._StickerPack_RemoveMaskCount_other = getValueWithForm(dict, "StickerPack.RemoveMaskCount", .other) + self._Watch_LastSeen_MinutesAgo_zero = getValueWithForm(dict, "Watch.LastSeen.MinutesAgo", .zero) + self._Watch_LastSeen_MinutesAgo_one = getValueWithForm(dict, "Watch.LastSeen.MinutesAgo", .one) + self._Watch_LastSeen_MinutesAgo_two = getValueWithForm(dict, "Watch.LastSeen.MinutesAgo", .two) + self._Watch_LastSeen_MinutesAgo_few = getValueWithForm(dict, "Watch.LastSeen.MinutesAgo", .few) + self._Watch_LastSeen_MinutesAgo_many = getValueWithForm(dict, "Watch.LastSeen.MinutesAgo", .many) + self._Watch_LastSeen_MinutesAgo_other = getValueWithForm(dict, "Watch.LastSeen.MinutesAgo", .other) self._LiveLocation_MenuChatsCount_zero = getValueWithForm(dict, "LiveLocation.MenuChatsCount", .zero) self._LiveLocation_MenuChatsCount_one = getValueWithForm(dict, "LiveLocation.MenuChatsCount", .one) self._LiveLocation_MenuChatsCount_two = getValueWithForm(dict, "LiveLocation.MenuChatsCount", .two) self._LiveLocation_MenuChatsCount_few = getValueWithForm(dict, "LiveLocation.MenuChatsCount", .few) self._LiveLocation_MenuChatsCount_many = getValueWithForm(dict, "LiveLocation.MenuChatsCount", .many) self._LiveLocation_MenuChatsCount_other = getValueWithForm(dict, "LiveLocation.MenuChatsCount", .other) + self._Notifications_Exceptions_zero = getValueWithForm(dict, "Notifications.Exceptions", .zero) + self._Notifications_Exceptions_one = getValueWithForm(dict, "Notifications.Exceptions", .one) + self._Notifications_Exceptions_two = getValueWithForm(dict, "Notifications.Exceptions", .two) + self._Notifications_Exceptions_few = getValueWithForm(dict, "Notifications.Exceptions", .few) + self._Notifications_Exceptions_many = getValueWithForm(dict, "Notifications.Exceptions", .many) + self._Notifications_Exceptions_other = getValueWithForm(dict, "Notifications.Exceptions", .other) + self._ForwardedLocations_zero = getValueWithForm(dict, "ForwardedLocations", .zero) + self._ForwardedLocations_one = getValueWithForm(dict, "ForwardedLocations", .one) + self._ForwardedLocations_two = getValueWithForm(dict, "ForwardedLocations", .two) + self._ForwardedLocations_few = getValueWithForm(dict, "ForwardedLocations", .few) + self._ForwardedLocations_many = getValueWithForm(dict, "ForwardedLocations", .many) + self._ForwardedLocations_other = getValueWithForm(dict, "ForwardedLocations", .other) + self._ServiceMessage_GameScoreSelfExtended_zero = getValueWithForm(dict, "ServiceMessage.GameScoreSelfExtended", .zero) + self._ServiceMessage_GameScoreSelfExtended_one = getValueWithForm(dict, "ServiceMessage.GameScoreSelfExtended", .one) + self._ServiceMessage_GameScoreSelfExtended_two = getValueWithForm(dict, "ServiceMessage.GameScoreSelfExtended", .two) + self._ServiceMessage_GameScoreSelfExtended_few = getValueWithForm(dict, "ServiceMessage.GameScoreSelfExtended", .few) + self._ServiceMessage_GameScoreSelfExtended_many = getValueWithForm(dict, "ServiceMessage.GameScoreSelfExtended", .many) + self._ServiceMessage_GameScoreSelfExtended_other = getValueWithForm(dict, "ServiceMessage.GameScoreSelfExtended", .other) + self._MessageTimer_Minutes_zero = getValueWithForm(dict, "MessageTimer.Minutes", .zero) + self._MessageTimer_Minutes_one = getValueWithForm(dict, "MessageTimer.Minutes", .one) + self._MessageTimer_Minutes_two = getValueWithForm(dict, "MessageTimer.Minutes", .two) + self._MessageTimer_Minutes_few = getValueWithForm(dict, "MessageTimer.Minutes", .few) + self._MessageTimer_Minutes_many = getValueWithForm(dict, "MessageTimer.Minutes", .many) + self._MessageTimer_Minutes_other = getValueWithForm(dict, "MessageTimer.Minutes", .other) + self._ServiceMessage_GameScoreSelfSimple_zero = getValueWithForm(dict, "ServiceMessage.GameScoreSelfSimple", .zero) + self._ServiceMessage_GameScoreSelfSimple_one = getValueWithForm(dict, "ServiceMessage.GameScoreSelfSimple", .one) + self._ServiceMessage_GameScoreSelfSimple_two = getValueWithForm(dict, "ServiceMessage.GameScoreSelfSimple", .two) + self._ServiceMessage_GameScoreSelfSimple_few = getValueWithForm(dict, "ServiceMessage.GameScoreSelfSimple", .few) + self._ServiceMessage_GameScoreSelfSimple_many = getValueWithForm(dict, "ServiceMessage.GameScoreSelfSimple", .many) + self._ServiceMessage_GameScoreSelfSimple_other = getValueWithForm(dict, "ServiceMessage.GameScoreSelfSimple", .other) + self._MuteExpires_Days_zero = getValueWithForm(dict, "MuteExpires.Days", .zero) + self._MuteExpires_Days_one = getValueWithForm(dict, "MuteExpires.Days", .one) + self._MuteExpires_Days_two = getValueWithForm(dict, "MuteExpires.Days", .two) + self._MuteExpires_Days_few = getValueWithForm(dict, "MuteExpires.Days", .few) + self._MuteExpires_Days_many = getValueWithForm(dict, "MuteExpires.Days", .many) + self._MuteExpires_Days_other = getValueWithForm(dict, "MuteExpires.Days", .other) + self._MessageTimer_Days_zero = getValueWithForm(dict, "MessageTimer.Days", .zero) + self._MessageTimer_Days_one = getValueWithForm(dict, "MessageTimer.Days", .one) + self._MessageTimer_Days_two = getValueWithForm(dict, "MessageTimer.Days", .two) + self._MessageTimer_Days_few = getValueWithForm(dict, "MessageTimer.Days", .few) + self._MessageTimer_Days_many = getValueWithForm(dict, "MessageTimer.Days", .many) + self._MessageTimer_Days_other = getValueWithForm(dict, "MessageTimer.Days", .other) + self._Conversation_StatusMembers_zero = getValueWithForm(dict, "Conversation.StatusMembers", .zero) + self._Conversation_StatusMembers_one = getValueWithForm(dict, "Conversation.StatusMembers", .one) + self._Conversation_StatusMembers_two = getValueWithForm(dict, "Conversation.StatusMembers", .two) + self._Conversation_StatusMembers_few = getValueWithForm(dict, "Conversation.StatusMembers", .few) + self._Conversation_StatusMembers_many = getValueWithForm(dict, "Conversation.StatusMembers", .many) + self._Conversation_StatusMembers_other = getValueWithForm(dict, "Conversation.StatusMembers", .other) + self._MuteExpires_Hours_zero = getValueWithForm(dict, "MuteExpires.Hours", .zero) + self._MuteExpires_Hours_one = getValueWithForm(dict, "MuteExpires.Hours", .one) + self._MuteExpires_Hours_two = getValueWithForm(dict, "MuteExpires.Hours", .two) + self._MuteExpires_Hours_few = getValueWithForm(dict, "MuteExpires.Hours", .few) + self._MuteExpires_Hours_many = getValueWithForm(dict, "MuteExpires.Hours", .many) + self._MuteExpires_Hours_other = getValueWithForm(dict, "MuteExpires.Hours", .other) + self._Call_ShortMinutes_zero = getValueWithForm(dict, "Call.ShortMinutes", .zero) + self._Call_ShortMinutes_one = getValueWithForm(dict, "Call.ShortMinutes", .one) + self._Call_ShortMinutes_two = getValueWithForm(dict, "Call.ShortMinutes", .two) + self._Call_ShortMinutes_few = getValueWithForm(dict, "Call.ShortMinutes", .few) + self._Call_ShortMinutes_many = getValueWithForm(dict, "Call.ShortMinutes", .many) + self._Call_ShortMinutes_other = getValueWithForm(dict, "Call.ShortMinutes", .other) + self._Notifications_ExceptionMuteExpires_Hours_zero = getValueWithForm(dict, "Notifications.ExceptionMuteExpires.Hours", .zero) + self._Notifications_ExceptionMuteExpires_Hours_one = getValueWithForm(dict, "Notifications.ExceptionMuteExpires.Hours", .one) + self._Notifications_ExceptionMuteExpires_Hours_two = getValueWithForm(dict, "Notifications.ExceptionMuteExpires.Hours", .two) + self._Notifications_ExceptionMuteExpires_Hours_few = getValueWithForm(dict, "Notifications.ExceptionMuteExpires.Hours", .few) + self._Notifications_ExceptionMuteExpires_Hours_many = getValueWithForm(dict, "Notifications.ExceptionMuteExpires.Hours", .many) + self._Notifications_ExceptionMuteExpires_Hours_other = getValueWithForm(dict, "Notifications.ExceptionMuteExpires.Hours", .other) + self._MessageTimer_ShortMinutes_zero = getValueWithForm(dict, "MessageTimer.ShortMinutes", .zero) + self._MessageTimer_ShortMinutes_one = getValueWithForm(dict, "MessageTimer.ShortMinutes", .one) + self._MessageTimer_ShortMinutes_two = getValueWithForm(dict, "MessageTimer.ShortMinutes", .two) + self._MessageTimer_ShortMinutes_few = getValueWithForm(dict, "MessageTimer.ShortMinutes", .few) + self._MessageTimer_ShortMinutes_many = getValueWithForm(dict, "MessageTimer.ShortMinutes", .many) + self._MessageTimer_ShortMinutes_other = getValueWithForm(dict, "MessageTimer.ShortMinutes", .other) + self._ForwardedVideos_zero = getValueWithForm(dict, "ForwardedVideos", .zero) + self._ForwardedVideos_one = getValueWithForm(dict, "ForwardedVideos", .one) + self._ForwardedVideos_two = getValueWithForm(dict, "ForwardedVideos", .two) + self._ForwardedVideos_few = getValueWithForm(dict, "ForwardedVideos", .few) + self._ForwardedVideos_many = getValueWithForm(dict, "ForwardedVideos", .many) + self._ForwardedVideos_other = getValueWithForm(dict, "ForwardedVideos", .other) + self._Call_ShortSeconds_zero = getValueWithForm(dict, "Call.ShortSeconds", .zero) + self._Call_ShortSeconds_one = getValueWithForm(dict, "Call.ShortSeconds", .one) + self._Call_ShortSeconds_two = getValueWithForm(dict, "Call.ShortSeconds", .two) + self._Call_ShortSeconds_few = getValueWithForm(dict, "Call.ShortSeconds", .few) + self._Call_ShortSeconds_many = getValueWithForm(dict, "Call.ShortSeconds", .many) + self._Call_ShortSeconds_other = getValueWithForm(dict, "Call.ShortSeconds", .other) + self._LastSeen_MinutesAgo_zero = getValueWithForm(dict, "LastSeen.MinutesAgo", .zero) + self._LastSeen_MinutesAgo_one = getValueWithForm(dict, "LastSeen.MinutesAgo", .one) + self._LastSeen_MinutesAgo_two = getValueWithForm(dict, "LastSeen.MinutesAgo", .two) + self._LastSeen_MinutesAgo_few = getValueWithForm(dict, "LastSeen.MinutesAgo", .few) + self._LastSeen_MinutesAgo_many = getValueWithForm(dict, "LastSeen.MinutesAgo", .many) + self._LastSeen_MinutesAgo_other = getValueWithForm(dict, "LastSeen.MinutesAgo", .other) + self._Media_ShareVideo_zero = getValueWithForm(dict, "Media.ShareVideo", .zero) + self._Media_ShareVideo_one = getValueWithForm(dict, "Media.ShareVideo", .one) + self._Media_ShareVideo_two = getValueWithForm(dict, "Media.ShareVideo", .two) + self._Media_ShareVideo_few = getValueWithForm(dict, "Media.ShareVideo", .few) + self._Media_ShareVideo_many = getValueWithForm(dict, "Media.ShareVideo", .many) + self._Media_ShareVideo_other = getValueWithForm(dict, "Media.ShareVideo", .other) + self._Notification_GameScoreSelfExtended_zero = getValueWithForm(dict, "Notification.GameScoreSelfExtended", .zero) + self._Notification_GameScoreSelfExtended_one = getValueWithForm(dict, "Notification.GameScoreSelfExtended", .one) + self._Notification_GameScoreSelfExtended_two = getValueWithForm(dict, "Notification.GameScoreSelfExtended", .two) + self._Notification_GameScoreSelfExtended_few = getValueWithForm(dict, "Notification.GameScoreSelfExtended", .few) + self._Notification_GameScoreSelfExtended_many = getValueWithForm(dict, "Notification.GameScoreSelfExtended", .many) + self._Notification_GameScoreSelfExtended_other = getValueWithForm(dict, "Notification.GameScoreSelfExtended", .other) + self._SharedMedia_DeleteItemsConfirmation_zero = getValueWithForm(dict, "SharedMedia.DeleteItemsConfirmation", .zero) + self._SharedMedia_DeleteItemsConfirmation_one = getValueWithForm(dict, "SharedMedia.DeleteItemsConfirmation", .one) + self._SharedMedia_DeleteItemsConfirmation_two = getValueWithForm(dict, "SharedMedia.DeleteItemsConfirmation", .two) + self._SharedMedia_DeleteItemsConfirmation_few = getValueWithForm(dict, "SharedMedia.DeleteItemsConfirmation", .few) + self._SharedMedia_DeleteItemsConfirmation_many = getValueWithForm(dict, "SharedMedia.DeleteItemsConfirmation", .many) + self._SharedMedia_DeleteItemsConfirmation_other = getValueWithForm(dict, "SharedMedia.DeleteItemsConfirmation", .other) + self._MessageTimer_ShortSeconds_zero = getValueWithForm(dict, "MessageTimer.ShortSeconds", .zero) + self._MessageTimer_ShortSeconds_one = getValueWithForm(dict, "MessageTimer.ShortSeconds", .one) + self._MessageTimer_ShortSeconds_two = getValueWithForm(dict, "MessageTimer.ShortSeconds", .two) + self._MessageTimer_ShortSeconds_few = getValueWithForm(dict, "MessageTimer.ShortSeconds", .few) + self._MessageTimer_ShortSeconds_many = getValueWithForm(dict, "MessageTimer.ShortSeconds", .many) + self._MessageTimer_ShortSeconds_other = getValueWithForm(dict, "MessageTimer.ShortSeconds", .other) + self._Conversation_StatusSubscribers_zero = getValueWithForm(dict, "Conversation.StatusSubscribers", .zero) + self._Conversation_StatusSubscribers_one = getValueWithForm(dict, "Conversation.StatusSubscribers", .one) + self._Conversation_StatusSubscribers_two = getValueWithForm(dict, "Conversation.StatusSubscribers", .two) + self._Conversation_StatusSubscribers_few = getValueWithForm(dict, "Conversation.StatusSubscribers", .few) + self._Conversation_StatusSubscribers_many = getValueWithForm(dict, "Conversation.StatusSubscribers", .many) + self._Conversation_StatusSubscribers_other = getValueWithForm(dict, "Conversation.StatusSubscribers", .other) + self._MessageTimer_ShortDays_zero = getValueWithForm(dict, "MessageTimer.ShortDays", .zero) + self._MessageTimer_ShortDays_one = getValueWithForm(dict, "MessageTimer.ShortDays", .one) + self._MessageTimer_ShortDays_two = getValueWithForm(dict, "MessageTimer.ShortDays", .two) + self._MessageTimer_ShortDays_few = getValueWithForm(dict, "MessageTimer.ShortDays", .few) + self._MessageTimer_ShortDays_many = getValueWithForm(dict, "MessageTimer.ShortDays", .many) + self._MessageTimer_ShortDays_other = getValueWithForm(dict, "MessageTimer.ShortDays", .other) + self._StickerPack_AddStickerCount_zero = getValueWithForm(dict, "StickerPack.AddStickerCount", .zero) + self._StickerPack_AddStickerCount_one = getValueWithForm(dict, "StickerPack.AddStickerCount", .one) + self._StickerPack_AddStickerCount_two = getValueWithForm(dict, "StickerPack.AddStickerCount", .two) + self._StickerPack_AddStickerCount_few = getValueWithForm(dict, "StickerPack.AddStickerCount", .few) + self._StickerPack_AddStickerCount_many = getValueWithForm(dict, "StickerPack.AddStickerCount", .many) + self._StickerPack_AddStickerCount_other = getValueWithForm(dict, "StickerPack.AddStickerCount", .other) + self._SharedMedia_Link_zero = getValueWithForm(dict, "SharedMedia.Link", .zero) + self._SharedMedia_Link_one = getValueWithForm(dict, "SharedMedia.Link", .one) + self._SharedMedia_Link_two = getValueWithForm(dict, "SharedMedia.Link", .two) + self._SharedMedia_Link_few = getValueWithForm(dict, "SharedMedia.Link", .few) + self._SharedMedia_Link_many = getValueWithForm(dict, "SharedMedia.Link", .many) + self._SharedMedia_Link_other = getValueWithForm(dict, "SharedMedia.Link", .other) + self._MessageTimer_Seconds_zero = getValueWithForm(dict, "MessageTimer.Seconds", .zero) + self._MessageTimer_Seconds_one = getValueWithForm(dict, "MessageTimer.Seconds", .one) + self._MessageTimer_Seconds_two = getValueWithForm(dict, "MessageTimer.Seconds", .two) + self._MessageTimer_Seconds_few = getValueWithForm(dict, "MessageTimer.Seconds", .few) + self._MessageTimer_Seconds_many = getValueWithForm(dict, "MessageTimer.Seconds", .many) + self._MessageTimer_Seconds_other = getValueWithForm(dict, "MessageTimer.Seconds", .other) + self._ServiceMessage_GameScoreSimple_zero = getValueWithForm(dict, "ServiceMessage.GameScoreSimple", .zero) + self._ServiceMessage_GameScoreSimple_one = getValueWithForm(dict, "ServiceMessage.GameScoreSimple", .one) + self._ServiceMessage_GameScoreSimple_two = getValueWithForm(dict, "ServiceMessage.GameScoreSimple", .two) + self._ServiceMessage_GameScoreSimple_few = getValueWithForm(dict, "ServiceMessage.GameScoreSimple", .few) + self._ServiceMessage_GameScoreSimple_many = getValueWithForm(dict, "ServiceMessage.GameScoreSimple", .many) + self._ServiceMessage_GameScoreSimple_other = getValueWithForm(dict, "ServiceMessage.GameScoreSimple", .other) + self._ForwardedPhotos_zero = getValueWithForm(dict, "ForwardedPhotos", .zero) + self._ForwardedPhotos_one = getValueWithForm(dict, "ForwardedPhotos", .one) + self._ForwardedPhotos_two = getValueWithForm(dict, "ForwardedPhotos", .two) + self._ForwardedPhotos_few = getValueWithForm(dict, "ForwardedPhotos", .few) + self._ForwardedPhotos_many = getValueWithForm(dict, "ForwardedPhotos", .many) + self._ForwardedPhotos_other = getValueWithForm(dict, "ForwardedPhotos", .other) + self._MessageTimer_Years_zero = getValueWithForm(dict, "MessageTimer.Years", .zero) + self._MessageTimer_Years_one = getValueWithForm(dict, "MessageTimer.Years", .one) + self._MessageTimer_Years_two = getValueWithForm(dict, "MessageTimer.Years", .two) + self._MessageTimer_Years_few = getValueWithForm(dict, "MessageTimer.Years", .few) + self._MessageTimer_Years_many = getValueWithForm(dict, "MessageTimer.Years", .many) + self._MessageTimer_Years_other = getValueWithForm(dict, "MessageTimer.Years", .other) + self._Forward_ConfirmMultipleFiles_zero = getValueWithForm(dict, "Forward.ConfirmMultipleFiles", .zero) + self._Forward_ConfirmMultipleFiles_one = getValueWithForm(dict, "Forward.ConfirmMultipleFiles", .one) + self._Forward_ConfirmMultipleFiles_two = getValueWithForm(dict, "Forward.ConfirmMultipleFiles", .two) + self._Forward_ConfirmMultipleFiles_few = getValueWithForm(dict, "Forward.ConfirmMultipleFiles", .few) + self._Forward_ConfirmMultipleFiles_many = getValueWithForm(dict, "Forward.ConfirmMultipleFiles", .many) + self._Forward_ConfirmMultipleFiles_other = getValueWithForm(dict, "Forward.ConfirmMultipleFiles", .other) + self._MessageTimer_ShortWeeks_zero = getValueWithForm(dict, "MessageTimer.ShortWeeks", .zero) + self._MessageTimer_ShortWeeks_one = getValueWithForm(dict, "MessageTimer.ShortWeeks", .one) + self._MessageTimer_ShortWeeks_two = getValueWithForm(dict, "MessageTimer.ShortWeeks", .two) + self._MessageTimer_ShortWeeks_few = getValueWithForm(dict, "MessageTimer.ShortWeeks", .few) + self._MessageTimer_ShortWeeks_many = getValueWithForm(dict, "MessageTimer.ShortWeeks", .many) + self._MessageTimer_ShortWeeks_other = getValueWithForm(dict, "MessageTimer.ShortWeeks", .other) + self._DialogList_LiveLocationChatsCount_zero = getValueWithForm(dict, "DialogList.LiveLocationChatsCount", .zero) + self._DialogList_LiveLocationChatsCount_one = getValueWithForm(dict, "DialogList.LiveLocationChatsCount", .one) + self._DialogList_LiveLocationChatsCount_two = getValueWithForm(dict, "DialogList.LiveLocationChatsCount", .two) + self._DialogList_LiveLocationChatsCount_few = getValueWithForm(dict, "DialogList.LiveLocationChatsCount", .few) + self._DialogList_LiveLocationChatsCount_many = getValueWithForm(dict, "DialogList.LiveLocationChatsCount", .many) + self._DialogList_LiveLocationChatsCount_other = getValueWithForm(dict, "DialogList.LiveLocationChatsCount", .other) + self._Notifications_ExceptionMuteExpires_Minutes_zero = getValueWithForm(dict, "Notifications.ExceptionMuteExpires.Minutes", .zero) + self._Notifications_ExceptionMuteExpires_Minutes_one = getValueWithForm(dict, "Notifications.ExceptionMuteExpires.Minutes", .one) + self._Notifications_ExceptionMuteExpires_Minutes_two = getValueWithForm(dict, "Notifications.ExceptionMuteExpires.Minutes", .two) + self._Notifications_ExceptionMuteExpires_Minutes_few = getValueWithForm(dict, "Notifications.ExceptionMuteExpires.Minutes", .few) + self._Notifications_ExceptionMuteExpires_Minutes_many = getValueWithForm(dict, "Notifications.ExceptionMuteExpires.Minutes", .many) + self._Notifications_ExceptionMuteExpires_Minutes_other = getValueWithForm(dict, "Notifications.ExceptionMuteExpires.Minutes", .other) + self._MessageTimer_Months_zero = getValueWithForm(dict, "MessageTimer.Months", .zero) + self._MessageTimer_Months_one = getValueWithForm(dict, "MessageTimer.Months", .one) + self._MessageTimer_Months_two = getValueWithForm(dict, "MessageTimer.Months", .two) + self._MessageTimer_Months_few = getValueWithForm(dict, "MessageTimer.Months", .few) + self._MessageTimer_Months_many = getValueWithForm(dict, "MessageTimer.Months", .many) + self._MessageTimer_Months_other = getValueWithForm(dict, "MessageTimer.Months", .other) + self._QuickSend_Photos_zero = getValueWithForm(dict, "QuickSend.Photos", .zero) + self._QuickSend_Photos_one = getValueWithForm(dict, "QuickSend.Photos", .one) + self._QuickSend_Photos_two = getValueWithForm(dict, "QuickSend.Photos", .two) + self._QuickSend_Photos_few = getValueWithForm(dict, "QuickSend.Photos", .few) + self._QuickSend_Photos_many = getValueWithForm(dict, "QuickSend.Photos", .many) + self._QuickSend_Photos_other = getValueWithForm(dict, "QuickSend.Photos", .other) + self._PrivacyLastSeenSettings_AddUsers_zero = getValueWithForm(dict, "PrivacyLastSeenSettings.AddUsers", .zero) + self._PrivacyLastSeenSettings_AddUsers_one = getValueWithForm(dict, "PrivacyLastSeenSettings.AddUsers", .one) + self._PrivacyLastSeenSettings_AddUsers_two = getValueWithForm(dict, "PrivacyLastSeenSettings.AddUsers", .two) + self._PrivacyLastSeenSettings_AddUsers_few = getValueWithForm(dict, "PrivacyLastSeenSettings.AddUsers", .few) + self._PrivacyLastSeenSettings_AddUsers_many = getValueWithForm(dict, "PrivacyLastSeenSettings.AddUsers", .many) + self._PrivacyLastSeenSettings_AddUsers_other = getValueWithForm(dict, "PrivacyLastSeenSettings.AddUsers", .other) + self._InviteText_ContactsCountText_zero = getValueWithForm(dict, "InviteText.ContactsCountText", .zero) + self._InviteText_ContactsCountText_one = getValueWithForm(dict, "InviteText.ContactsCountText", .one) + self._InviteText_ContactsCountText_two = getValueWithForm(dict, "InviteText.ContactsCountText", .two) + self._InviteText_ContactsCountText_few = getValueWithForm(dict, "InviteText.ContactsCountText", .few) + self._InviteText_ContactsCountText_many = getValueWithForm(dict, "InviteText.ContactsCountText", .many) + self._InviteText_ContactsCountText_other = getValueWithForm(dict, "InviteText.ContactsCountText", .other) + self._SharedMedia_Generic_zero = getValueWithForm(dict, "SharedMedia.Generic", .zero) + self._SharedMedia_Generic_one = getValueWithForm(dict, "SharedMedia.Generic", .one) + self._SharedMedia_Generic_two = getValueWithForm(dict, "SharedMedia.Generic", .two) + self._SharedMedia_Generic_few = getValueWithForm(dict, "SharedMedia.Generic", .few) + self._SharedMedia_Generic_many = getValueWithForm(dict, "SharedMedia.Generic", .many) + self._SharedMedia_Generic_other = getValueWithForm(dict, "SharedMedia.Generic", .other) + self._StickerPack_StickerCount_zero = getValueWithForm(dict, "StickerPack.StickerCount", .zero) + self._StickerPack_StickerCount_one = getValueWithForm(dict, "StickerPack.StickerCount", .one) + self._StickerPack_StickerCount_two = getValueWithForm(dict, "StickerPack.StickerCount", .two) + self._StickerPack_StickerCount_few = getValueWithForm(dict, "StickerPack.StickerCount", .few) + self._StickerPack_StickerCount_many = getValueWithForm(dict, "StickerPack.StickerCount", .many) + self._StickerPack_StickerCount_other = getValueWithForm(dict, "StickerPack.StickerCount", .other) + self._PasscodeSettings_FailedAttempts_zero = getValueWithForm(dict, "PasscodeSettings.FailedAttempts", .zero) + self._PasscodeSettings_FailedAttempts_one = getValueWithForm(dict, "PasscodeSettings.FailedAttempts", .one) + self._PasscodeSettings_FailedAttempts_two = getValueWithForm(dict, "PasscodeSettings.FailedAttempts", .two) + self._PasscodeSettings_FailedAttempts_few = getValueWithForm(dict, "PasscodeSettings.FailedAttempts", .few) + self._PasscodeSettings_FailedAttempts_many = getValueWithForm(dict, "PasscodeSettings.FailedAttempts", .many) + self._PasscodeSettings_FailedAttempts_other = getValueWithForm(dict, "PasscodeSettings.FailedAttempts", .other) + self._Map_ETAMinutes_zero = getValueWithForm(dict, "Map.ETAMinutes", .zero) + self._Map_ETAMinutes_one = getValueWithForm(dict, "Map.ETAMinutes", .one) + self._Map_ETAMinutes_two = getValueWithForm(dict, "Map.ETAMinutes", .two) + self._Map_ETAMinutes_few = getValueWithForm(dict, "Map.ETAMinutes", .few) + self._Map_ETAMinutes_many = getValueWithForm(dict, "Map.ETAMinutes", .many) + self._Map_ETAMinutes_other = getValueWithForm(dict, "Map.ETAMinutes", .other) + self._Watch_LastSeen_HoursAgo_zero = getValueWithForm(dict, "Watch.LastSeen.HoursAgo", .zero) + self._Watch_LastSeen_HoursAgo_one = getValueWithForm(dict, "Watch.LastSeen.HoursAgo", .one) + self._Watch_LastSeen_HoursAgo_two = getValueWithForm(dict, "Watch.LastSeen.HoursAgo", .two) + self._Watch_LastSeen_HoursAgo_few = getValueWithForm(dict, "Watch.LastSeen.HoursAgo", .few) + self._Watch_LastSeen_HoursAgo_many = getValueWithForm(dict, "Watch.LastSeen.HoursAgo", .many) + self._Watch_LastSeen_HoursAgo_other = getValueWithForm(dict, "Watch.LastSeen.HoursAgo", .other) + self._SharedMedia_Video_zero = getValueWithForm(dict, "SharedMedia.Video", .zero) + self._SharedMedia_Video_one = getValueWithForm(dict, "SharedMedia.Video", .one) + self._SharedMedia_Video_two = getValueWithForm(dict, "SharedMedia.Video", .two) + self._SharedMedia_Video_few = getValueWithForm(dict, "SharedMedia.Video", .few) + self._SharedMedia_Video_many = getValueWithForm(dict, "SharedMedia.Video", .many) + self._SharedMedia_Video_other = getValueWithForm(dict, "SharedMedia.Video", .other) + self._ForwardedVideoMessages_zero = getValueWithForm(dict, "ForwardedVideoMessages", .zero) + self._ForwardedVideoMessages_one = getValueWithForm(dict, "ForwardedVideoMessages", .one) + self._ForwardedVideoMessages_two = getValueWithForm(dict, "ForwardedVideoMessages", .two) + self._ForwardedVideoMessages_few = getValueWithForm(dict, "ForwardedVideoMessages", .few) + self._ForwardedVideoMessages_many = getValueWithForm(dict, "ForwardedVideoMessages", .many) + self._ForwardedVideoMessages_other = getValueWithForm(dict, "ForwardedVideoMessages", .other) + self._Passport_Scans_zero = getValueWithForm(dict, "Passport.Scans", .zero) + self._Passport_Scans_one = getValueWithForm(dict, "Passport.Scans", .one) + self._Passport_Scans_two = getValueWithForm(dict, "Passport.Scans", .two) + self._Passport_Scans_few = getValueWithForm(dict, "Passport.Scans", .few) + self._Passport_Scans_many = getValueWithForm(dict, "Passport.Scans", .many) + self._Passport_Scans_other = getValueWithForm(dict, "Passport.Scans", .other) + self._Watch_UserInfo_Mute_zero = getValueWithForm(dict, "Watch.UserInfo.Mute", .zero) + self._Watch_UserInfo_Mute_one = getValueWithForm(dict, "Watch.UserInfo.Mute", .one) + self._Watch_UserInfo_Mute_two = getValueWithForm(dict, "Watch.UserInfo.Mute", .two) + self._Watch_UserInfo_Mute_few = getValueWithForm(dict, "Watch.UserInfo.Mute", .few) + self._Watch_UserInfo_Mute_many = getValueWithForm(dict, "Watch.UserInfo.Mute", .many) + self._Watch_UserInfo_Mute_other = getValueWithForm(dict, "Watch.UserInfo.Mute", .other) + self._SharedMedia_File_zero = getValueWithForm(dict, "SharedMedia.File", .zero) + self._SharedMedia_File_one = getValueWithForm(dict, "SharedMedia.File", .one) + self._SharedMedia_File_two = getValueWithForm(dict, "SharedMedia.File", .two) + self._SharedMedia_File_few = getValueWithForm(dict, "SharedMedia.File", .few) + self._SharedMedia_File_many = getValueWithForm(dict, "SharedMedia.File", .many) + self._SharedMedia_File_other = getValueWithForm(dict, "SharedMedia.File", .other) + self._Media_SharePhoto_zero = getValueWithForm(dict, "Media.SharePhoto", .zero) + self._Media_SharePhoto_one = getValueWithForm(dict, "Media.SharePhoto", .one) + self._Media_SharePhoto_two = getValueWithForm(dict, "Media.SharePhoto", .two) + self._Media_SharePhoto_few = getValueWithForm(dict, "Media.SharePhoto", .few) + self._Media_SharePhoto_many = getValueWithForm(dict, "Media.SharePhoto", .many) + self._Media_SharePhoto_other = getValueWithForm(dict, "Media.SharePhoto", .other) + self._StickerPack_AddMaskCount_zero = getValueWithForm(dict, "StickerPack.AddMaskCount", .zero) + self._StickerPack_AddMaskCount_one = getValueWithForm(dict, "StickerPack.AddMaskCount", .one) + self._StickerPack_AddMaskCount_two = getValueWithForm(dict, "StickerPack.AddMaskCount", .two) + self._StickerPack_AddMaskCount_few = getValueWithForm(dict, "StickerPack.AddMaskCount", .few) + self._StickerPack_AddMaskCount_many = getValueWithForm(dict, "StickerPack.AddMaskCount", .many) + self._StickerPack_AddMaskCount_other = getValueWithForm(dict, "StickerPack.AddMaskCount", .other) + self._UserCount_zero = getValueWithForm(dict, "UserCount", .zero) + self._UserCount_one = getValueWithForm(dict, "UserCount", .one) + self._UserCount_two = getValueWithForm(dict, "UserCount", .two) + self._UserCount_few = getValueWithForm(dict, "UserCount", .few) + self._UserCount_many = getValueWithForm(dict, "UserCount", .many) + self._UserCount_other = getValueWithForm(dict, "UserCount", .other) + self._Conversation_LiveLocationMembersCount_zero = getValueWithForm(dict, "Conversation.LiveLocationMembersCount", .zero) + self._Conversation_LiveLocationMembersCount_one = getValueWithForm(dict, "Conversation.LiveLocationMembersCount", .one) + self._Conversation_LiveLocationMembersCount_two = getValueWithForm(dict, "Conversation.LiveLocationMembersCount", .two) + self._Conversation_LiveLocationMembersCount_few = getValueWithForm(dict, "Conversation.LiveLocationMembersCount", .few) + self._Conversation_LiveLocationMembersCount_many = getValueWithForm(dict, "Conversation.LiveLocationMembersCount", .many) + self._Conversation_LiveLocationMembersCount_other = getValueWithForm(dict, "Conversation.LiveLocationMembersCount", .other) + self._Notifications_ExceptionMuteExpires_Days_zero = getValueWithForm(dict, "Notifications.ExceptionMuteExpires.Days", .zero) + self._Notifications_ExceptionMuteExpires_Days_one = getValueWithForm(dict, "Notifications.ExceptionMuteExpires.Days", .one) + self._Notifications_ExceptionMuteExpires_Days_two = getValueWithForm(dict, "Notifications.ExceptionMuteExpires.Days", .two) + self._Notifications_ExceptionMuteExpires_Days_few = getValueWithForm(dict, "Notifications.ExceptionMuteExpires.Days", .few) + self._Notifications_ExceptionMuteExpires_Days_many = getValueWithForm(dict, "Notifications.ExceptionMuteExpires.Days", .many) + self._Notifications_ExceptionMuteExpires_Days_other = getValueWithForm(dict, "Notifications.ExceptionMuteExpires.Days", .other) + self._MessageTimer_Hours_zero = getValueWithForm(dict, "MessageTimer.Hours", .zero) + self._MessageTimer_Hours_one = getValueWithForm(dict, "MessageTimer.Hours", .one) + self._MessageTimer_Hours_two = getValueWithForm(dict, "MessageTimer.Hours", .two) + self._MessageTimer_Hours_few = getValueWithForm(dict, "MessageTimer.Hours", .few) + self._MessageTimer_Hours_many = getValueWithForm(dict, "MessageTimer.Hours", .many) + self._MessageTimer_Hours_other = getValueWithForm(dict, "MessageTimer.Hours", .other) + self._LiveLocationUpdated_MinutesAgo_zero = getValueWithForm(dict, "LiveLocationUpdated.MinutesAgo", .zero) + self._LiveLocationUpdated_MinutesAgo_one = getValueWithForm(dict, "LiveLocationUpdated.MinutesAgo", .one) + self._LiveLocationUpdated_MinutesAgo_two = getValueWithForm(dict, "LiveLocationUpdated.MinutesAgo", .two) + self._LiveLocationUpdated_MinutesAgo_few = getValueWithForm(dict, "LiveLocationUpdated.MinutesAgo", .few) + self._LiveLocationUpdated_MinutesAgo_many = getValueWithForm(dict, "LiveLocationUpdated.MinutesAgo", .many) + self._LiveLocationUpdated_MinutesAgo_other = getValueWithForm(dict, "LiveLocationUpdated.MinutesAgo", .other) } } diff --git a/TelegramUI/RecentSessionsController.swift b/TelegramUI/RecentSessionsController.swift index 3b39454ddc..142ae230e1 100644 --- a/TelegramUI/RecentSessionsController.swift +++ b/TelegramUI/RecentSessionsController.swift @@ -32,6 +32,7 @@ private enum RecentSessionsMode: Int { private enum RecentSessionsSection: Int32 { case currentSession + case pendingSessions case otherSessions } @@ -72,7 +73,8 @@ private enum RecentSessionsEntry: ItemListNodeEntry { case terminateOtherSessions(PresentationTheme, String) case terminateAllWebSessions(PresentationTheme, String) case currentSessionInfo(PresentationTheme, String) - + case pendingSessionsHeader(PresentationTheme, String) + case pendingSession(index: Int32, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, session: RecentAccountSession, enabled: Bool, editing: Bool, revealed: Bool) case otherSessionsHeader(PresentationTheme, String) case session(index: Int32, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, session: RecentAccountSession, enabled: Bool, editing: Bool, revealed: Bool) case website(index: Int32, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, website: WebAuthorization, peer: Peer?, enabled: Bool, editing: Bool, revealed: Bool) @@ -81,6 +83,8 @@ private enum RecentSessionsEntry: ItemListNodeEntry { switch self { case .currentSessionHeader, .currentSession, .terminateOtherSessions, .terminateAllWebSessions, .currentSessionInfo: return RecentSessionsSection.currentSession.rawValue + case .pendingSessionsHeader, .pendingSession: + return RecentSessionsSection.pendingSessions.rawValue case .otherSessionsHeader, .session, .website: return RecentSessionsSection.otherSessions.rawValue } @@ -98,8 +102,12 @@ private enum RecentSessionsEntry: ItemListNodeEntry { return .index(3) case .currentSessionInfo: return .index(4) - case .otherSessionsHeader: + case .pendingSessionsHeader: return .index(5) + case let .pendingSession(_, _, _, _, session, _, _, _): + return .session(session.hash) + case .otherSessionsHeader: + return .index(6) case let .session(_, _, _, _, session, _, _, _): return .session(session.hash) case let .website(_, _, _, _, website, _, _, _, _): @@ -133,6 +141,18 @@ private enum RecentSessionsEntry: ItemListNodeEntry { } else { return false } + case let .pendingSessionsHeader(lhsTheme, lhsText): + if case let .pendingSessionsHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { + return true + } else { + return false + } + case let .pendingSession(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsSession, lhsEnabled, lhsEditing, lhsRevealed): + if case let .pendingSession(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsSession, rhsEnabled, rhsEditing, rhsRevealed) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsSession == rhsSession, lhsEnabled == rhsEnabled, lhsEditing == rhsEditing, lhsRevealed == rhsRevealed { + return true + } else { + return false + } case let .otherSessionsHeader(lhsTheme, lhsText): if case let .otherSessionsHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { return true @@ -166,7 +186,11 @@ private enum RecentSessionsEntry: ItemListNodeEntry { if case let .index(rhsIndex) = rhs.stableId { return lhsIndex <= rhsIndex } else { - return true + if case .pendingSession = rhs, lhsIndex > 5 { + return false + } else { + return true + } } case .session: switch lhs { @@ -176,6 +200,18 @@ private enum RecentSessionsEntry: ItemListNodeEntry { } else { return false } + case let .pendingSession(lhsIndex, _, _, _, _, _, _, _): + if case let .pendingSession(rhsIndex, _, _, _, _, _, _, _) = rhs { + return lhsIndex <= rhsIndex + } else if case .session = rhs { + return true + } else { + if case let .index(rhsIndex) = rhs.stableId { + return rhsIndex == 6 + } else { + return false + } + } case let .website(lhsIndex, _, _, _, _, _, _, _, _): if case let .website(rhsIndex, _, _, _, _, _, _, _, _) = rhs { return lhsIndex <= rhsIndex @@ -206,8 +242,16 @@ private enum RecentSessionsEntry: ItemListNodeEntry { }) case let .currentSessionInfo(theme, text): return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section) + case let .pendingSessionsHeader(theme, text): + return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section) case let .otherSessionsHeader(theme, text): return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section) + case let .pendingSession(_, theme, strings, dateTimeFormat, session, enabled, editing, revealed): + return ItemListRecentSessionItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, session: session, enabled: enabled, editable: true, editing: editing, revealed: revealed, sectionId: self.section, setSessionIdWithRevealedOptions: { previousId, id in + arguments.setSessionIdWithRevealedOptions(previousId, id) + }, removeSession: { id in + arguments.removeSession(id) + }) case let .session(_, theme, strings, dateTimeFormat, session, enabled, editing, revealed): return ItemListRecentSessionItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, session: session, enabled: enabled, editable: true, editing: editing, revealed: revealed, sectionId: self.section, setSessionIdWithRevealedOptions: { previousId, id in arguments.setSessionIdWithRevealedOptions(previousId, id) @@ -293,6 +337,18 @@ private func recentSessionsControllerEntries(presentationData: PresentationData, entries.append(.terminateOtherSessions(presentationData.theme, presentationData.strings.AuthSessions_TerminateOtherSessions)) entries.append(.currentSessionInfo(presentationData.theme, presentationData.strings.AuthSessions_TerminateOtherSessionsHelp)) + let filteredPendingSessions: [RecentAccountSession] = sessions.filter({ $0.flags.contains(.passwordPending) }) + if !filteredPendingSessions.isEmpty { + entries.append(.pendingSessionsHeader(presentationData.theme, presentationData.strings.AuthSessions_PasswordPending)) + + for i in 0 ..< filteredPendingSessions.count { + if !existingSessionIds.contains(filteredPendingSessions[i].hash) { + existingSessionIds.insert(filteredPendingSessions[i].hash) + entries.append(.pendingSession(index: Int32(i), theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, session: filteredPendingSessions[i], enabled: state.removingSessionId != filteredPendingSessions[i].hash && !state.terminatingOtherSessions, editing: state.editing, revealed: state.sessionIdWithRevealedOptions == filteredPendingSessions[i].hash)) + } + } + } + entries.append(.otherSessionsHeader(presentationData.theme, presentationData.strings.AuthSessions_OtherSessions)) let filteredSessions: [RecentAccountSession] = sessions.sorted(by: { lhs, rhs in @@ -300,9 +356,9 @@ private func recentSessionsControllerEntries(presentationData: PresentationData, }) for i in 0 ..< filteredSessions.count { - if !existingSessionIds.contains(sessions[i].hash) { - existingSessionIds.insert(sessions[i].hash) - entries.append(.session(index: Int32(i), theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, session: sessions[i], enabled: state.removingSessionId != sessions[i].hash && !state.terminatingOtherSessions, editing: state.editing, revealed: state.sessionIdWithRevealedOptions == sessions[i].hash)) + if !existingSessionIds.contains(filteredSessions[i].hash) { + existingSessionIds.insert(filteredSessions[i].hash) + entries.append(.session(index: Int32(i), theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, session: filteredSessions[i], enabled: state.removingSessionId != filteredSessions[i].hash && !state.terminatingOtherSessions, editing: state.editing, revealed: state.sessionIdWithRevealedOptions == filteredSessions[i].hash)) } } } diff --git a/TelegramUI/SettingsController.swift b/TelegramUI/SettingsController.swift index 3a646f3cb7..0e1f2aae40 100644 --- a/TelegramUI/SettingsController.swift +++ b/TelegramUI/SettingsController.swift @@ -714,12 +714,7 @@ public func settingsController(account: Account, accountManager: AccountManager) actionsDisposable.dispose() } - let icon: UIImage? - if (useSpecialTabBarIcons()) { - icon = UIImage(bundleImageName: "Chat List/Tabs/IconSettingsHW") - } else { - icon = UIImage(bundleImageName: "Chat List/Tabs/IconSettings") - } + let icon = UIImage(bundleImageName: "Chat List/Tabs/IconSettings") let controller = ItemListController(account: account, state: signal, tabBarItem: (account.applicationContext as! TelegramApplicationContext).presentationData |> map { presentationData in return ItemListControllerTabBarItem(title: presentationData.strings.Settings_Title, image: icon, selectedImage: icon) diff --git a/TelegramUI/TelegramController.swift b/TelegramUI/TelegramController.swift index 1275d2ad2c..b0e9cb2446 100644 --- a/TelegramUI/TelegramController.swift +++ b/TelegramUI/TelegramController.swift @@ -39,6 +39,7 @@ private func presentLiveLocationController(account: Account, peerId: PeerId, con } |> deliverOnMainQueue).start(next: presentImpl) } else if let liveLocationManager = account.telegramApplicationContext.liveLocationManager { let _ = (liveLocationManager.summaryManager.peersBroadcastingTo(peerId: peerId) + |> take(1) |> map { peersAndMessages -> Message? in return peersAndMessages?.first?.1 } |> deliverOnMainQueue).start(next: presentImpl) diff --git a/TelegramUI/UniversalVideoCalleryItem.swift b/TelegramUI/UniversalVideoCalleryItem.swift index c804893cac..580293834e 100644 --- a/TelegramUI/UniversalVideoCalleryItem.swift +++ b/TelegramUI/UniversalVideoCalleryItem.swift @@ -18,11 +18,13 @@ class UniversalVideoGalleryItem: GalleryItem { let originData: GalleryItemOriginData? let indexData: GalleryItemIndexData? let contentInfo: UniversalVideoGalleryItemContentInfo? - let caption: String + let caption: NSAttributedString let hideControls: Bool let playbackCompleted: () -> Void + let openUrl: (String) -> Void + let openUrlOptions: (String) -> Void - init(account: Account, presentationData: PresentationData, content: UniversalVideoContent, originData: GalleryItemOriginData?, indexData: GalleryItemIndexData?, contentInfo: UniversalVideoGalleryItemContentInfo?, caption: String, hideControls: Bool = false, playbackCompleted: @escaping () -> Void = {}) { + init(account: Account, presentationData: PresentationData, content: UniversalVideoContent, originData: GalleryItemOriginData?, indexData: GalleryItemIndexData?, contentInfo: UniversalVideoGalleryItemContentInfo?, caption: NSAttributedString, hideControls: Bool = false, playbackCompleted: @escaping () -> Void = {}, openUrl: @escaping (String) -> Void, openUrlOptions: @escaping (String) -> Void) { self.account = account self.presentationData = presentationData self.content = content @@ -32,10 +34,12 @@ class UniversalVideoGalleryItem: GalleryItem { self.caption = caption self.hideControls = hideControls self.playbackCompleted = playbackCompleted + self.openUrl = openUrl + self.openUrlOptions = openUrlOptions } func node() -> GalleryItemNode { - let node = UniversalVideoGalleryItemNode(account: self.account, presentationData: self.presentationData) + let node = UniversalVideoGalleryItemNode(account: self.account, presentationData: self.presentationData, openUrl: self.openUrl, openUrlOptions: self.openUrlOptions) if let indexData = self.indexData { node._title.set(.single("\(indexData.position + 1) \(self.presentationData.strings.Common_of) \(indexData.totalCount)")) @@ -150,13 +154,15 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { var playbackCompleted: (() -> Void)? - init(account: Account, presentationData: PresentationData) { + init(account: Account, presentationData: PresentationData, openUrl: @escaping (String) -> Void, openUrlOptions: @escaping (String) -> Void) { self.account = account self.strings = presentationData.strings self.scrubberView = ChatVideoGalleryItemScrubberView() self.footerContentNode = ChatItemGalleryFooterContentNode(account: account, presentationData: presentationData) self.footerContentNode.scrubberView = self.scrubberView + self.footerContentNode.openUrl = openUrl + self.footerContentNode.openUrlOptions = openUrlOptions self.statusButtonNode = HighlightableButtonNode() self.statusNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.5)) diff --git a/TelegramUI/WatchPresetSettings.swift b/TelegramUI/WatchPresetSettings.swift index 2674bbc2d4..8e017dccde 100644 --- a/TelegramUI/WatchPresetSettings.swift +++ b/TelegramUI/WatchPresetSettings.swift @@ -29,7 +29,7 @@ public struct WatchPresetSettings: PreferencesEntry, Equatable { public func encode(_ encoder: PostboxEncoder) { let keys = self.customPresets.keys.sorted() - let values = keys.reduce([String]()) { (values, index) -> [String] in + let values = keys.reduce([String]()) { (values, index) in var values = values if let value = self.customPresets[index] { values.append(value)