From cc17271034e4ed6732fc9e9966623b044af9d8ae Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sun, 11 Nov 2018 18:03:00 +0400 Subject: [PATCH] Send non opaque images from pasteboard as stickers Language suggestion alert Ability to create new contact from "tel:"-link context menu and chat search Upload date display for profile pictures Instant View improvements --- TelegramUI.xcodeproj/project.pbxproj | 36 +- TelegramUI/Accessibility.swift | 4 - TelegramUI/ApplicationSpecificData.swift | 6 - ...uthorizationSequenceSplashController.swift | 20 +- TelegramUI/AvatarGalleryController.swift | 20 +- .../AvatarGalleryItemFooterContentNode.swift | 72 +- TelegramUI/CallListController.swift | 4 +- TelegramUI/ChatController.swift | 117 +- TelegramUI/ChatControllerInteraction.swift | 4 +- TelegramUI/ChatControllerNode.swift | 10 +- .../ChatInterfaceStateContextMenus.swift | 4 +- .../ChatItemGalleryFooterContentNode.swift | 32 + TelegramUI/ChatListController.swift | 63 +- TelegramUI/ChatListControllerNode.swift | 5 + TelegramUI/ChatListNode.swift | 6 +- TelegramUI/ChatListSearchContainerNode.swift | 48 +- TelegramUI/ChatListSearchItemHeader.swift | 3 + ...ChatMessageActionSheetControllerNode.swift | 2 +- TelegramUI/ChatMessageBubbleItemNode.swift | 8 +- TelegramUI/ChatMessageReplyInfoNode.swift | 35 +- TelegramUI/ChatMessageStickerItemNode.swift | 2 +- .../ChatMessageWebpageBubbleContentNode.swift | 10 +- .../ChatRecentActionsControllerNode.swift | 1 + TelegramUI/ChatTextInputPanelNode.swift | 34 +- TelegramUI/ContactAddItem.swift | 251 +++ TelegramUI/DeviceContactInfoController.swift | 3 +- TelegramUI/FetchCachedRepresentations.swift | 2 +- TelegramUI/GalleryControllerNode.swift | 28 +- .../GalleryThumbnailContainerNode.swift | 4 +- TelegramUI/GeneratedMediaStoreSettings.swift | 12 +- TelegramUI/GeoLocation.swift | 85 - TelegramUI/HashtagSearchController.swift | 1 + TelegramUI/ImageTransparency.swift | 55 + TelegramUI/InstantImageGalleryItem.swift | 12 +- TelegramUI/InstantPageControllerNode.swift | 33 +- TelegramUI/InstantPageDetailsItem.swift | 9 +- TelegramUI/InstantPageGalleryController.swift | 41 +- .../InstantPageGalleryFooterContentNode.swift | 24 +- TelegramUI/InstantPageImageItem.swift | 6 +- TelegramUI/InstantPageImageNode.swift | 22 +- TelegramUI/InstantPageLayout.swift | 87 +- TelegramUI/InstantPageMedia.swift | 4 +- TelegramUI/InstantPageSlideshowItemNode.swift | 4 +- TelegramUI/InstantPageTableItem.swift | 95 +- TelegramUI/InstantPageTextItem.swift | 8 +- TelegramUI/InstantPageTheme.swift | 10 +- TelegramUI/InstantPageTile.swift | 11 + TelegramUI/InstantPageWebEmbedItem.swift | 2 +- TelegramUI/LanguageSuggestionController.swift | 361 ++++ TelegramUI/ListMessageFileItemNode.swift | 14 +- TelegramUI/OngoingCallContext.swift | 16 +- TelegramUI/OngoingCallThreadLocalContext.h | 8 +- TelegramUI/OngoingCallThreadLocalContext.mm | 17 +- TelegramUI/OpenAddContact.swift | 20 + TelegramUI/OpenChatMessage.swift | 2 +- TelegramUI/OverlayPlayerControllerNode.swift | 1 + TelegramUI/OverlayPlayerControlsNode.swift | 8 +- TelegramUI/PeerAvatarImageGalleryItem.swift | 24 +- .../PeerMediaCollectionController.swift | 1 + TelegramUI/PeerSelectionControllerNode.swift | 2 +- TelegramUI/PhotoResources.swift | 13 +- TelegramUI/PresentationCall.swift | 4 +- TelegramUI/PresentationCallManager.swift | 4 +- TelegramUI/PresentationStrings.swift | 1839 +++++++++-------- .../Resources/PresentationStrings.mapping | Bin 86768 -> 86824 bytes TelegramUI/SettingsController.swift | 6 - TelegramUI/ShareController.swift | 60 +- .../StickerPackPreviewControllerNode.swift | 34 +- TelegramUI/StickerResources.swift | 20 +- TelegramUI/ThemeSettingsChatPreviewItem.swift | 1 + TelegramUI/TimestampStrings.swift | 3 - TelegramUI/UIImage+WebP.h | 1 + TelegramUI/UIImage+WebP.m | 72 + TelegramUI/UniversalVideoGalleryItem.swift | 8 +- TelegramUI/WebP.swift | 31 +- 75 files changed, 2526 insertions(+), 1399 deletions(-) delete mode 100644 TelegramUI/ApplicationSpecificData.swift create mode 100644 TelegramUI/ContactAddItem.swift delete mode 100644 TelegramUI/GeoLocation.swift create mode 100644 TelegramUI/ImageTransparency.swift create mode 100644 TelegramUI/LanguageSuggestionController.swift create mode 100644 TelegramUI/OpenAddContact.swift delete mode 100644 TelegramUI/TimestampStrings.swift diff --git a/TelegramUI.xcodeproj/project.pbxproj b/TelegramUI.xcodeproj/project.pbxproj index de1679671c..f49d7585d0 100644 --- a/TelegramUI.xcodeproj/project.pbxproj +++ b/TelegramUI.xcodeproj/project.pbxproj @@ -7,6 +7,10 @@ objects = { /* Begin PBXBuildFile section */ + 0902838821931D960067EFBD /* LanguageSuggestionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0902838721931D960067EFBD /* LanguageSuggestionController.swift */; }; + 0902838D2194AEB90067EFBD /* ImageTransparency.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0902838C2194AEB90067EFBD /* ImageTransparency.swift */; }; + 090E63E62195880F00E3C035 /* ContactAddItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 090E63E52195880F00E3C035 /* ContactAddItem.swift */; }; + 090E63EE2196FE3A00E3C035 /* OpenAddContact.swift in Sources */ = {isa = PBXBuildFile; fileRef = 090E63ED2196FE3A00E3C035 /* OpenAddContact.swift */; }; 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 */; }; @@ -87,7 +91,6 @@ D01776BA1F1D704F0044446D /* RadialStatusIconContentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01776B91F1D704F0044446D /* RadialStatusIconContentNode.swift */; }; D01776BC1F1E21AF0044446D /* RadialStatusBackgroundNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01776BB1F1E21AF0044446D /* RadialStatusBackgroundNode.swift */; }; D01776BE1F1E76920044446D /* PeerMediaCollectionSectionsNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01776BD1F1E76920044446D /* PeerMediaCollectionSectionsNode.swift */; }; - D018477E1FFBC01E00075256 /* TimestampStrings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D018477D1FFBC01E00075256 /* TimestampStrings.swift */; }; D01847801FFBD12E00075256 /* ChatListPresentationData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D018477F1FFBD12E00075256 /* ChatListPresentationData.swift */; }; D0185E882089ED5F005E1A6C /* ProxyListSettingsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0185E872089ED5F005E1A6C /* ProxyListSettingsController.swift */; }; D0185E8A208A01AF005E1A6C /* ProxySettingsActionItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0185E89208A01AF005E1A6C /* ProxySettingsActionItem.swift */; }; @@ -583,11 +586,9 @@ D0EC6CC81EB9F58800EBF1C3 /* ProgressiveImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F69E931D6B8C9B0046BCD6 /* ProgressiveImage.swift */; }; D0EC6CC91EB9F58800EBF1C3 /* WebP.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F69E941D6B8C9B0046BCD6 /* WebP.swift */; }; D0EC6CCA1EB9F58800EBF1C3 /* PeerPresenceStatusManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B844571DAC44E8005F29E1 /* PeerPresenceStatusManager.swift */; }; - D0EC6CCB1EB9F58800EBF1C3 /* ApplicationSpecificData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0ED5D4A1DC806D7007CBB15 /* ApplicationSpecificData.swift */; }; D0EC6CCC1EB9F58800EBF1C3 /* ServiceSoundManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D073CE641DCBC26B007511FD /* ServiceSoundManager.swift */; }; D0EC6CCD1EB9F58800EBF1C3 /* DeclareEncodables.swift in Sources */ = {isa = PBXBuildFile; fileRef = D073CE701DCBF23F007511FD /* DeclareEncodables.swift */; }; D0EC6CCE1EB9F58800EBF1C3 /* TelegramApplicationContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05811931DD5F9380057C769 /* TelegramApplicationContext.swift */; }; - D0EC6CCF1EB9F58800EBF1C3 /* GeoLocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04B66B71DD672D00049C3D2 /* GeoLocation.swift */; }; D0EC6CD11EB9F58800EBF1C3 /* UrlHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = D023836F1DDF0462004018B6 /* UrlHandling.swift */; }; D0EC6CD31EB9F58800EBF1C3 /* GenerateTextEntities.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F917B41E0DA396003687E6 /* GenerateTextEntities.swift */; }; D0EC6CD41EB9F58800EBF1C3 /* StringWithAppliedEntities.swift in Sources */ = {isa = PBXBuildFile; fileRef = D017494D1E1059570057C89A /* StringWithAppliedEntities.swift */; }; @@ -1055,6 +1056,10 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 0902838721931D960067EFBD /* LanguageSuggestionController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LanguageSuggestionController.swift; sourceTree = ""; }; + 0902838C2194AEB90067EFBD /* ImageTransparency.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageTransparency.swift; sourceTree = ""; }; + 090E63E52195880F00E3C035 /* ContactAddItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactAddItem.swift; sourceTree = ""; }; + 090E63ED2196FE3A00E3C035 /* OpenAddContact.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenAddContact.swift; sourceTree = ""; }; 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 = ""; }; @@ -1179,7 +1184,6 @@ D01776BD1F1E76920044446D /* PeerMediaCollectionSectionsNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerMediaCollectionSectionsNode.swift; sourceTree = ""; }; D0177B7F1DFAE18500A5083A /* MediaPlayerTimeTextNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaPlayerTimeTextNode.swift; sourceTree = ""; }; D0177B831DFB095000A5083A /* FileMediaResourceStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileMediaResourceStatus.swift; sourceTree = ""; }; - D018477D1FFBC01E00075256 /* TimestampStrings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimestampStrings.swift; sourceTree = ""; }; D018477F1FFBD12E00075256 /* ChatListPresentationData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListPresentationData.swift; sourceTree = ""; }; D0185E872089ED5F005E1A6C /* ProxyListSettingsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyListSettingsController.swift; sourceTree = ""; }; D0185E89208A01AF005E1A6C /* ProxySettingsActionItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxySettingsActionItem.swift; sourceTree = ""; }; @@ -1379,7 +1383,6 @@ D04B4D101EEA04D400711AF6 /* MapResources.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MapResources.swift; sourceTree = ""; }; D04B4D121EEA0A6500711AF6 /* ChatMessageMapBubbleContentNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatMessageMapBubbleContentNode.swift; sourceTree = ""; }; D04B4D651EEA993A00711AF6 /* LegacyLocationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyLocationController.swift; sourceTree = ""; }; - D04B66B71DD672D00049C3D2 /* GeoLocation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeoLocation.swift; sourceTree = ""; }; D04BB2B21E44E56200650E93 /* AuthorizationSequenceSplashController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthorizationSequenceSplashController.swift; sourceTree = ""; }; D04BB2B41E44E58E00650E93 /* AuthorizationSequencePhoneEntryController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthorizationSequencePhoneEntryController.swift; sourceTree = ""; }; D04BB2B81E44E5E400650E93 /* AuthorizationSequencePhoneEntryControllerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthorizationSequencePhoneEntryControllerNode.swift; sourceTree = ""; }; @@ -2027,7 +2030,6 @@ D0EC6EBC1EBA100F00EBF1C3 /* CoreAudio.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreAudio.framework; path = System/Library/Frameworks/CoreAudio.framework; sourceTree = SDKROOT; }; D0EC6FFA1EBA1DE900EBF1C3 /* OngoingCallThreadLocalContext.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OngoingCallThreadLocalContext.h; sourceTree = ""; }; D0EC6FFC1EBA1F2400EBF1C3 /* OngoingCallThreadLocalContext.mm */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; fileEncoding = 4; path = OngoingCallThreadLocalContext.mm; sourceTree = ""; }; - D0ED5D4A1DC806D7007CBB15 /* ApplicationSpecificData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApplicationSpecificData.swift; sourceTree = ""; }; D0EE97191D88BCA0006C18E1 /* ChatInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatInfo.swift; sourceTree = ""; }; D0EEE9A02165585F001292A6 /* DocumentPreviewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentPreviewController.swift; sourceTree = ""; }; D0EF40DC1E72F00E000DFCD4 /* SelectivePrivacySettingsPeersController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelectivePrivacySettingsPeersController.swift; sourceTree = ""; }; @@ -2230,6 +2232,14 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 0902838921931DA30067EFBD /* Language Suggestion */ = { + isa = PBXGroup; + children = ( + 0902838721931D960067EFBD /* LanguageSuggestionController.swift */, + ); + name = "Language Suggestion"; + sourceTree = ""; + }; 092F368B2154AAD6001A9F49 /* Fonts */ = { isa = PBXGroup; children = ( @@ -2427,6 +2437,7 @@ D0192D3B210A44D00005FA10 /* DeviceContactData.swift */, D0192D43210A5AA50005FA10 /* DeviceContactDataManager.swift */, D0F4B0212110972300912B92 /* ContactInfoStrings.swift */, + 090E63ED2196FE3A00E3C035 /* OpenAddContact.swift */, ); name = "Device Contacts"; sourceTree = ""; @@ -3122,6 +3133,7 @@ D0F69E6F1D6B8C340046BCD6 /* ContactsPeerItem.swift */, D0F69E711D6B8C340046BCD6 /* ContactsSectionHeaderAccessoryItem.swift */, D08775131E3F4A7700A97350 /* ContactListNameIndexHeader.swift */, + 090E63E52195880F00E3C035 /* ContactAddItem.swift */, ); name = "Contact List Node"; sourceTree = ""; @@ -4340,6 +4352,7 @@ D0F69E791D6B8C3B0046BCD6 /* Settings */ = { isa = PBXGroup; children = ( + 0902838921931DA30067EFBD /* Language Suggestion */, D02C816F2177715A00CD1006 /* Notifications */, D0FA0AC21E7742CE005BB9B7 /* Privacy and Security */, D0C9323A1E0B4AD40074F044 /* Data and Storage */, @@ -4405,11 +4418,9 @@ D0F69E931D6B8C9B0046BCD6 /* ProgressiveImage.swift */, D0F69E941D6B8C9B0046BCD6 /* WebP.swift */, D0B844571DAC44E8005F29E1 /* PeerPresenceStatusManager.swift */, - D0ED5D4A1DC806D7007CBB15 /* ApplicationSpecificData.swift */, D073CE641DCBC26B007511FD /* ServiceSoundManager.swift */, D073CE701DCBF23F007511FD /* DeclareEncodables.swift */, D05811931DD5F9380057C769 /* TelegramApplicationContext.swift */, - D04B66B71DD672D00049C3D2 /* GeoLocation.swift */, D023836F1DDF0462004018B6 /* UrlHandling.swift */, D0F917B41E0DA396003687E6 /* GenerateTextEntities.swift */, D017494D1E1059570057C89A /* StringWithAppliedEntities.swift */, @@ -4432,7 +4443,6 @@ D01C06BF1FBF118A001561AB /* MessageUtils.swift */, D0C26D5D1FDF49E7004ABF18 /* DateFormat.swift */, D09250051FE5371D003F693F /* GlobalExperimentalSettings.swift */, - D018477D1FFBC01E00075256 /* TimestampStrings.swift */, D0C26D561FDF2388004ABF18 /* OpenChatMessage.swift */, D04ECD711FFBF22B00DE9029 /* OpenUrl.swift */, D0FC194C201F82A000FEDBB2 /* OpenResolvedUrl.swift */, @@ -4452,6 +4462,7 @@ D0192D45210F4F940005FA10 /* FixSearchableListNodeScrolling.swift */, 09C3466C2167D63A00B76780 /* Accessibility.swift */, D0068FA721760FA300D1B315 /* StoreDownloadedMedia.swift */, + 0902838C2194AEB90067EFBD /* ImageTransparency.swift */, ); name = Utils; sourceTree = ""; @@ -4874,7 +4885,6 @@ D081E108217F583F003CD921 /* LanguageLinkPreviewContentNode.swift in Sources */, D0EC6CC91EB9F58800EBF1C3 /* WebP.swift in Sources */, D0EC6CCA1EB9F58800EBF1C3 /* PeerPresenceStatusManager.swift in Sources */, - D0EC6CCB1EB9F58800EBF1C3 /* ApplicationSpecificData.swift in Sources */, D09E637F1F0E8C9F003444CD /* PeerMessagesMediaPlaylist.swift in Sources */, D0E412DF206AA00500BEE4A2 /* SecureIdVerificationDocumentsContext.swift in Sources */, D0EC6CCC1EB9F58800EBF1C3 /* ServiceSoundManager.swift in Sources */, @@ -4883,7 +4893,6 @@ D02D60B1206C189900FEFE1E /* SecureIdPlaintextFormController.swift in Sources */, D0CFBB951FD8B05000B65C0D /* OverlayInstantVideoDecoration.swift in Sources */, D0EC6CCE1EB9F58800EBF1C3 /* TelegramApplicationContext.swift in Sources */, - D0EC6CCF1EB9F58800EBF1C3 /* GeoLocation.swift in Sources */, D09394132007F5BB00997F31 /* LocationBroadcastNavigationAccessoryPanel.swift in Sources */, D0471B5C1EFEB4F30074D609 /* BotPaymentFieldItemNode.swift in Sources */, D0C27B3D1F4B454800A4E170 /* InstantPagePlayableVideoNode.swift in Sources */, @@ -5089,6 +5098,7 @@ D0E8B8B9204477B600605593 /* SecretChatKeyVisualization.swift in Sources */, D0EC6D371EB9F58800EBF1C3 /* SearchDisplayController.swift in Sources */, D0185E8C208A025A005E1A6C /* ProxySettingsServerItem.swift in Sources */, + 090E63EE2196FE3A00E3C035 /* OpenAddContact.swift in Sources */, D04281ED200E3B28009DDE36 /* ItemListControllerSearch.swift in Sources */, D0EC6D381EB9F58800EBF1C3 /* SearchDisplayControllerContentNode.swift in Sources */, D06F1EA41F6C0A5D00FE8B74 /* ChatHistorySearchContainerNode.swift in Sources */, @@ -5236,6 +5246,7 @@ D0EC6D8F1EB9F58800EBF1C3 /* ChatMessageBubbleContentCalclulateImageCorners.swift in Sources */, D01776BC1F1E21AF0044446D /* RadialStatusBackgroundNode.swift in Sources */, D0FE4DE61F0BA58A00E8A0B3 /* OverlayMediaItemNode.swift in Sources */, + 0902838821931D960067EFBD /* LanguageSuggestionController.swift in Sources */, D0E8B8A72044339500605593 /* PresentationCallToneData.swift in Sources */, D0F19F6420E5A15B00EEC860 /* ChatMediaInputPeerSpecificItem.swift in Sources */, D0AEAE252080D6830013176E /* StickerPaneSearchContainerNode.swift in Sources */, @@ -5302,7 +5313,6 @@ D0EC6DA71EB9F58900EBF1C3 /* ChatMessageBackground.swift in Sources */, D0F0AAE01EC1E12C005EE2A5 /* PresentationCall.swift in Sources */, D0EC6DA81EB9F58900EBF1C3 /* ChatInterfaceState.swift in Sources */, - D018477E1FFBC01E00075256 /* TimestampStrings.swift in Sources */, D08BDF661FA8CB10009D08E1 /* EditSettingsController.swift in Sources */, D0EC6DA91EB9F58900EBF1C3 /* ChatPresentationInterfaceState.swift in Sources */, D0EC6DAA1EB9F58900EBF1C3 /* ChatPanelInterfaceInteraction.swift in Sources */, @@ -5467,6 +5477,7 @@ D0E412CE206A707400BEE4A2 /* FormControllerTextItem.swift in Sources */, D0EC6E051EB9F58900EBF1C3 /* SecretMediaPreviewControllerNode.swift in Sources */, D007019C2029E8F2006B9E34 /* LegqacyICloudFileController.swift in Sources */, + 0902838D2194AEB90067EFBD /* ImageTransparency.swift in Sources */, D0208AD61FA33D14001F0D5F /* RaiseToListenActivator.m in Sources */, D0EC6E061EB9F58900EBF1C3 /* ChatDocumentGalleryItem.swift in Sources */, D0EC6E071EB9F58900EBF1C3 /* ChatExternalFileGalleryItem.swift in Sources */, @@ -5502,6 +5513,7 @@ 0952D1772177FB5400194860 /* WatchPresetSettings.swift in Sources */, D091C7A61F8ECEA300D7DE13 /* SettingsThemeWallpaperNode.swift in Sources */, D0EC6E191EB9F58900EBF1C3 /* InstantPageAnchorItem.swift in Sources */, + 090E63E62195880F00E3C035 /* ContactAddItem.swift in Sources */, D05677531F4CA0D0001B723E /* InstantPagePeerReferenceNode.swift in Sources */, D0EC6E1A1EB9F58900EBF1C3 /* InstantPageImageItem.swift in Sources */, D0EC6E1B1EB9F58900EBF1C3 /* InstantPageImageNode.swift in Sources */, diff --git a/TelegramUI/Accessibility.swift b/TelegramUI/Accessibility.swift index 72790096df..d0b28162d9 100644 --- a/TelegramUI/Accessibility.swift +++ b/TelegramUI/Accessibility.swift @@ -1,10 +1,6 @@ import SwiftSignalKit import UIKit -func currentReduceMotionEnabled() -> Bool { - return UIAccessibility.isReduceMotionEnabled -} - func reduceMotionEnabled() -> Signal { return Signal { subscriber in subscriber.putNext(UIAccessibility.isReduceMotionEnabled) diff --git a/TelegramUI/ApplicationSpecificData.swift b/TelegramUI/ApplicationSpecificData.swift deleted file mode 100644 index 6d66cdf0b9..0000000000 --- a/TelegramUI/ApplicationSpecificData.swift +++ /dev/null @@ -1,6 +0,0 @@ -import Foundation -import SwiftSignalKit - -public final class ApplicationSpecificData { - -} diff --git a/TelegramUI/AuthorizationSequenceSplashController.swift b/TelegramUI/AuthorizationSequenceSplashController.swift index e24b4818d2..d4545b1cb8 100644 --- a/TelegramUI/AuthorizationSequenceSplashController.swift +++ b/TelegramUI/AuthorizationSequenceSplashController.swift @@ -21,6 +21,7 @@ final class AuthorizationSequenceSplashController: ViewController { var nextPressed: ((PresentationStrings?) -> Void)? + private let suggestedLocalization = Promise(nil) private let activateLocalizationDisposable = MetaDisposable() init(postbox: Postbox, network: Network, theme: AuthorizationTheme) { @@ -28,8 +29,11 @@ final class AuthorizationSequenceSplashController: ViewController { self.network = network self.theme = theme + self.suggestedLocalization.set(currentlySuggestedLocalization(network: network, extractKeys: ["Login.ContinueWithLocalization"])) + let suggestedLocalization = self.suggestedLocalization + let localizationSignal = SSignal(generator: { subscriber in - let disposable = currentlySuggestedLocalization(network: network, extractKeys: ["Login.ContinueWithLocalization"]).start(next: { localization in + let disposable = suggestedLocalization.get().start(next: { localization in guard let localization = localization else { return } @@ -138,17 +142,27 @@ final class AuthorizationSequenceSplashController: ViewController { } private func activateLocalization(_ code: String) { - let _ = (postbox.transaction { transaction -> String in + let currentCode = self.postbox.transaction { transaction -> String in if let current = transaction.getPreferencesEntry(key: PreferencesKeys.localizationSettings) as? LocalizationSettings { return current.primaryComponent.languageCode } else { return "en" } - } |> deliverOnMainQueue).start(next: { [weak self] currentCode in + } + let suggestedCode = self.suggestedLocalization.get() + |> map { localization -> String? in + return localization?.availableLocalizations.first?.languageCode + } + + let _ = (combineLatest(currentCode, suggestedCode) |> deliverOnMainQueue).start(next: { [weak self] currentCode, suggestedCode in guard let strongSelf = self else { return } + if let suggestedCode = suggestedCode { + _ = markSuggestedLocalizationAsSeenInteractively(postbox: strongSelf.postbox, languageCode: suggestedCode).start() + } + if currentCode == code { strongSelf.nextPressed?(nil) return diff --git a/TelegramUI/AvatarGalleryController.swift b/TelegramUI/AvatarGalleryController.swift index 5342e21a02..083e019949 100644 --- a/TelegramUI/AvatarGalleryController.swift +++ b/TelegramUI/AvatarGalleryController.swift @@ -8,13 +8,13 @@ import TelegramCore enum AvatarGalleryEntry: Equatable { case topImage([TelegramMediaImageRepresentation], GalleryItemIndexData?) - case image(TelegramMediaImage, GalleryItemIndexData?) + case image(TelegramMediaImage, Peer, Int32, GalleryItemIndexData?) var representations: [TelegramMediaImageRepresentation] { switch self { case let .topImage(representations, _): return representations - case let .image(image, _): + case let .image(image, _, _, _): return image.representations } } @@ -23,7 +23,7 @@ enum AvatarGalleryEntry: Equatable { switch self { case let .topImage(_, indexData): return indexData - case let .image(_, indexData): + case let .image(_, _, _, indexData): return indexData } } @@ -36,8 +36,8 @@ enum AvatarGalleryEntry: Equatable { } else { return false } - case let .image(lhsImage, lhsIndexData): - if case let .image(rhsImage, rhsIndexData) = rhs, lhsImage.isEqual(to: rhsImage), lhsIndexData == rhsIndexData { + case let .image(lhsImage, lhsPeer, lhsDate, lhsIndexData): + if case let .image(rhsImage, rhsPeer, rhsDate, rhsIndexData) = rhs, lhsImage.isEqual(to: rhsImage), arePeersEqual(lhsPeer, rhsPeer), lhsDate == rhsDate, lhsIndexData == rhsIndexData { return true } else { return false @@ -80,9 +80,9 @@ func fetchedAvatarGalleryEntries(account: Account, peer: Peer) -> Signal<[Avatar let indexData = GalleryItemIndexData(position: index, totalCount: Int32(photos.count)) if result.isEmpty, let first = initialEntries.first { let image = TelegramMediaImage(imageId: photo.image.imageId, representations: first.representations, reference: photo.reference, partialReference: nil) - result.append(.image(image, indexData)) + result.append(.image(image, peer, photo.date, indexData)) } else { - result.append(.image(photo.image, indexData)) + result.append(.image(photo.image, peer, photo.date, indexData)) } index += 1 } @@ -166,7 +166,7 @@ class AvatarGalleryController: ViewController { strongSelf.entries = entries strongSelf.centralEntryIndex = 0 if strongSelf.isViewLoaded { - strongSelf.galleryNode.pager.replaceItems(strongSelf.entries.map({ entry in PeerAvatarImageGalleryItem(account: account, peer: peer, strings: presentationData.strings, entry: entry, delete: strongSelf.peer.id == strongSelf.account.peerId ? { + strongSelf.galleryNode.pager.replaceItems(strongSelf.entries.map({ entry in PeerAvatarImageGalleryItem(account: account, peer: peer, presentationData: presentationData, entry: entry, delete: strongSelf.peer.id == strongSelf.account.peerId ? { self?.deleteEntry(entry) } : nil) }), centralItemIndex: 0, keepFirst: true) @@ -302,7 +302,7 @@ class AvatarGalleryController: ViewController { } let presentationData = self.presentationData - self.galleryNode.pager.replaceItems(self.entries.map({ entry in PeerAvatarImageGalleryItem(account: self.account, peer: peer, strings: presentationData.strings, entry: entry, delete: self.peer.id == self.account.peerId ? { [weak self] in + self.galleryNode.pager.replaceItems(self.entries.map({ entry in PeerAvatarImageGalleryItem(account: self.account, peer: peer, presentationData: presentationData, entry: entry, delete: self.peer.id == self.account.peerId ? { [weak self] in self?.deleteEntry(entry) } : nil) }), centralItemIndex: self.centralEntryIndex) @@ -387,7 +387,7 @@ class AvatarGalleryController: ViewController { switch entry { case .topImage: break - case let .image(image, _): + case let .image(image, _, _, _): if let reference = image.reference { let _ = removeAccountPhoto(network: self.account.network, reference: reference).start() } diff --git a/TelegramUI/AvatarGalleryItemFooterContentNode.swift b/TelegramUI/AvatarGalleryItemFooterContentNode.swift index e359b2a790..e4dc25e28b 100644 --- a/TelegramUI/AvatarGalleryItemFooterContentNode.swift +++ b/TelegramUI/AvatarGalleryItemFooterContentNode.swift @@ -9,11 +9,21 @@ import Photos private let deleteImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionThrash"), color: .white) private let actionImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionAction"), color: .white) +private let nameFont = Font.medium(15.0) +private let dateFont = Font.regular(14.0) + final class AvatarGalleryItemFooterContentNode: GalleryFooterContentNode { private let account: Account + private var strings: PresentationStrings + private var dateTimeFormat: PresentationDateTimeFormat private let deleteButton: UIButton private let actionButton: UIButton + private let nameNode: ASTextNode + private let dateNode: ASTextNode + + private var currentNameText: String? + private var currentDateText: String? var delete: (() -> Void)? { didSet { @@ -23,8 +33,10 @@ final class AvatarGalleryItemFooterContentNode: GalleryFooterContentNode { var share: ((GalleryControllerInteraction) -> Void)? - init(account: Account) { + init(account: Account, presentationData: PresentationData) { self.account = account + self.strings = presentationData.strings + self.dateTimeFormat = presentationData.dateTimeFormat self.deleteButton = UIButton() self.deleteButton.isHidden = true @@ -33,11 +45,24 @@ final class AvatarGalleryItemFooterContentNode: GalleryFooterContentNode { self.deleteButton.setImage(deleteImage, for: [.normal]) self.actionButton.setImage(actionImage, for: [.normal]) + self.nameNode = ASTextNode() + self.nameNode.maximumNumberOfLines = 1 + self.nameNode.isLayerBacked = true + self.nameNode.displaysAsynchronously = false + + self.dateNode = ASTextNode() + self.dateNode.maximumNumberOfLines = 1 + self.dateNode.isLayerBacked = true + self.dateNode.displaysAsynchronously = false + super.init() self.view.addSubview(self.deleteButton) self.view.addSubview(self.actionButton) + self.addSubnode(self.nameNode) + self.addSubnode(self.dateNode) + self.deleteButton.addTarget(self, action: #selector(self.deleteButtonPressed), for: [.touchUpInside]) self.actionButton.addTarget(self, action: #selector(self.actionButtonPressed), for: [.touchUpInside]) } @@ -45,6 +70,35 @@ final class AvatarGalleryItemFooterContentNode: GalleryFooterContentNode { deinit { } + func setEntry(_ entry: AvatarGalleryEntry) { + var nameText: String? + var dateText: String? + switch entry { + case let .image(_, peer, date, _): + nameText = peer.displayTitle + dateText = humanReadableStringForTimestamp(strings: self.strings, dateTimeFormat: self.dateTimeFormat, timestamp: date) + default: + break + } + + if self.currentNameText != nameText || self.currentDateText != dateText { + self.currentNameText = nameText + self.currentDateText = dateText + + if let nameText = nameText { + self.nameNode.attributedText = NSAttributedString(string: nameText, font: nameFont, textColor: .white) + } else { + self.nameNode.attributedText = nil + } + + if let dateText = dateText { + self.dateNode.attributedText = NSAttributedString(string: dateText, font: dateFont, textColor: .white) + } else { + self.dateNode.attributedText = nil + } + } + } + override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, contentInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat { var panelHeight: CGFloat = 44.0 + bottomInset panelHeight += contentInset @@ -52,17 +106,33 @@ final class AvatarGalleryItemFooterContentNode: GalleryFooterContentNode { self.actionButton.frame = CGRect(origin: CGPoint(x: leftInset, y: panelHeight - bottomInset - 44.0), size: CGSize(width: 44.0, height: 44.0)) self.deleteButton.frame = CGRect(origin: CGPoint(x: width - 44.0 - rightInset, y: panelHeight - bottomInset - 44.0), size: CGSize(width: 44.0, height: 44.0)) + let nameSize = self.nameNode.measure(CGSize(width: width - 44.0 * 2.0 - 8.0 * 2.0 - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude)) + let dateSize = self.dateNode.measure(CGSize(width: width - 44.0 * 2.0 - 8.0 * 2.0, height: CGFloat.greatestFiniteMagnitude)) + + if nameSize.height.isZero { + self.dateNode.frame = CGRect(origin: CGPoint(x: floor((width - dateSize.width) / 2.0), y: panelHeight - bottomInset - 44.0 + floor((44.0 - dateSize.height) / 2.0)), size: dateSize) + } else { + let labelsSpacing: CGFloat = 0.0 + self.nameNode.frame = CGRect(origin: CGPoint(x: floor((width - nameSize.width) / 2.0), y: panelHeight - bottomInset - 44.0 + floor((44.0 - dateSize.height - nameSize.height - labelsSpacing) / 2.0)), size: nameSize) + self.dateNode.frame = CGRect(origin: CGPoint(x: floor((width - dateSize.width) / 2.0), y: panelHeight - bottomInset - 44.0 + floor((44.0 - dateSize.height - nameSize.height - labelsSpacing) / 2.0) + nameSize.height + labelsSpacing), size: dateSize) + } + return panelHeight } override func animateIn(fromHeight: CGFloat, previousContentNode: GalleryFooterContentNode, transition: ContainedViewLayoutTransition) { self.deleteButton.alpha = 1.0 self.actionButton.alpha = 1.0 + self.nameNode.alpha = 1.0 + self.dateNode.alpha = 1.0 } override func animateOut(toHeight: CGFloat, nextContentNode: GalleryFooterContentNode, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) { self.deleteButton.alpha = 0.0 self.actionButton.alpha = 0.0 + self.nameNode.alpha = 0.0 + self.dateNode.alpha = 0.0 + completion() } @objc private func deleteButtonPressed() { diff --git a/TelegramUI/CallListController.swift b/TelegramUI/CallListController.swift index 11abbc3e26..e7fb079e1c 100644 --- a/TelegramUI/CallListController.swift +++ b/TelegramUI/CallListController.swift @@ -93,9 +93,11 @@ public final class CallListController: ViewController { } private func updateThemeAndStrings() { + let index = self.segmentedTitleView.index self.segmentedTitleView.segments = [self.presentationData.strings.Calls_All, self.presentationData.strings.Calls_Missed] self.segmentedTitleView.color = self.presentationData.theme.rootController.navigationBar.accentTextColor - + self.segmentedTitleView.index = index + self.tabBarItem.title = self.presentationData.strings.Calls_TabTitle self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) switch self.mode { diff --git a/TelegramUI/ChatController.swift b/TelegramUI/ChatController.swift index 8492e0f91d..be03eb8969 100644 --- a/TelegramUI/ChatController.swift +++ b/TelegramUI/ChatController.swift @@ -402,7 +402,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID var hasActions = false for media in updatedMessages[0].media { - if media is TelegramMediaAction { + if media is TelegramMediaAction || media is TelegramMediaExpiredContent { hasActions = true break } @@ -780,13 +780,15 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID let mailtoString = "mailto:" let telString = "tel:" var openText = strongSelf.presentationData.strings.Conversation_LinkDialogOpen + var phoneNumber: String? if cleanUrl.hasPrefix(mailtoString) { canAddToReadingList = false cleanUrl = String(cleanUrl[cleanUrl.index(cleanUrl.startIndex, offsetBy: mailtoString.distance(from: mailtoString.startIndex, to: mailtoString.endIndex))...]) } else if cleanUrl.hasPrefix(telString) { canAddToReadingList = false - cleanUrl = String(cleanUrl[cleanUrl.index(cleanUrl.startIndex, offsetBy: telString.distance(from: telString.startIndex, to: telString.endIndex))...]) - openText = strongSelf.presentationData.strings.Conversation_Call + phoneNumber = String(cleanUrl[cleanUrl.index(cleanUrl.startIndex, offsetBy: telString.distance(from: telString.startIndex, to: telString.endIndex))...]) + cleanUrl = phoneNumber! + openText = strongSelf.presentationData.strings.UserInfo_PhoneCall } else if canOpenIn { openText = strongSelf.presentationData.strings.Conversation_FileOpenIn } @@ -804,6 +806,14 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID } } })) + if let phoneNumber = phoneNumber { + items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_AddContact, color: .accent, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + if let strongSelf = self { + strongSelf.controllerInteraction?.addContact(phoneNumber) + } + })) + } items.append(ActionSheetButtonItem(title: canAddToReadingList ? strongSelf.presentationData.strings.ShareMenu_CopyShareLink : strongSelf.presentationData.strings.Conversation_ContextMenuCopy, color: .accent, action: { [weak actionSheet] in actionSheet?.dismissAnimated() UIPasteboard.general.string = cleanUrl @@ -1010,6 +1020,12 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID strongSelf.chatDisplayNode.dismissInput() strongSelf.present(actionSheet, in: .window(.root)) }) + }, addContact: { [weak self] phoneNumber in + if let strongSelf = self { + openAddContact(account: strongSelf.account, phoneNumber: phoneNumber, present: { [weak self] controller, arguments in + self?.present(controller, in: .window(.root), with: arguments) + }) + } }, requestMessageUpdate: { [weak self] id in if let strongSelf = self { strongSelf.chatDisplayNode.historyNode.requestMessageUpdate(id) @@ -1835,26 +1851,14 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID strongSelf.presentAttachmentMenu(editMediaOptions: nil) } } - - let postbox = self.account.postbox - self.chatDisplayNode.displayPasteMenu = { [weak self] images in - let _ = (postbox.transaction { transaction -> GeneratedMediaStoreSettings in - let entry = transaction.getPreferencesEntry(key: ApplicationSpecificPreferencesKeys.generatedMediaStoreSettings) as? GeneratedMediaStoreSettings - return entry ?? GeneratedMediaStoreSettings.defaultSettings - } - |> deliverOnMainQueue).start(next: { [weak self] settings in - if let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer { - let controller = legacyPasteMenu(account: strongSelf.account, peer: peer, saveEditedPhotos: settings.storeEditedPhotos, allowGrouping: true, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, images: images, sendMessagesWithSignals: { signals in - self?.enqueueMediaMessages(signals: signals) - }) - strongSelf.chatDisplayNode.dismissInput() - strongSelf.present(controller, in: .window(.root)) - } - }) - } - self.chatDisplayNode.sendGif = { [weak self] data in - if let strongSelf = self { - strongSelf.enqueueGifData(data) + self.chatDisplayNode.paste = { [weak self] data in + switch data { + case let .images(images): + self?.displayPasteMenu(images) + case let .gif(data): + self?.enqueueGifData(data) + case let .sticker(image): + self?.enqueueStickerImage(image) } } self.chatDisplayNode.updateTypingActivity = { [weak self] value in @@ -2798,12 +2802,17 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID self.failedMessageEventsDisposable.set((self.account.pendingMessageManager.failedMessageEvents(peerId: peerId) |> deliverOnMainQueue).start(next: { [weak self] reason in if let strongSelf = self { + let text: String switch reason { case .flood: - strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: strongSelf.presentationData.theme), title: nil, text: strongSelf.presentationData.strings.Conversation_SendMessageErrorFlood, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Generic_ErrorMoreInfo, action: { - self?.openPeerMention("spambot", navigation: .chat(textInputState: nil, messageId: nil)) - }), TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + text = strongSelf.presentationData.strings.Conversation_SendMessageErrorFlood + case .publicBan: + text = strongSelf.presentationData.strings.Conversation_SendMessageErrorGroupRestricted + } + strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: strongSelf.presentationData.theme), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Generic_ErrorMoreInfo, action: { + self?.openPeerMention("spambot", navigation: .chat(textInputState: nil, messageId: nil)) + }), TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) } })) case let .group(groupId): @@ -3848,6 +3857,22 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID } } + private func displayPasteMenu(_ images: [UIImage]) { + let _ = (self.account.postbox.transaction { transaction -> GeneratedMediaStoreSettings in + let entry = transaction.getPreferencesEntry(key: ApplicationSpecificPreferencesKeys.generatedMediaStoreSettings) as? GeneratedMediaStoreSettings + return entry ?? GeneratedMediaStoreSettings.defaultSettings + } + |> deliverOnMainQueue).start(next: { [weak self] settings in + if let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer { + let controller = legacyPasteMenu(account: strongSelf.account, peer: peer, saveEditedPhotos: settings.storeEditedPhotos, allowGrouping: true, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, images: images, sendMessagesWithSignals: { signals in + self?.enqueueMediaMessages(signals: signals) + }) + strongSelf.chatDisplayNode.dismissInput() + strongSelf.present(controller, in: .window(.root)) + } + }) + } + private func enqueueGifData(_ data: Data) { self.enqueueMediaMessageDisposable.set((legacyEnqueueGifMessage(account: self.account, data: data) |> deliverOnMainQueue).start(next: { [weak self] message in if let strongSelf = self { @@ -3864,6 +3889,34 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID })) } + private func enqueueStickerImage(_ image: UIImage) { + let size = image.size.aspectFitted(CGSize(width: 512.0, height: 512.0)) + self.enqueueMediaMessageDisposable.set((convertToWebP(image: image, targetSize: size, quality: 0.85) |> deliverOnMainQueue).start(next: { [weak self] data in + if let strongSelf = self, !data.isEmpty { + let resource = LocalFileMediaResource(fileId: arc4random64()) + strongSelf.account.postbox.mediaBox.storeResourceData(resource.id, data: data) + + var fileAttributes: [TelegramMediaFileAttribute] = [] + fileAttributes.append(.FileName(fileName: "sticker.webp")) + fileAttributes.append(.Sticker(displayText: "", packReference: nil, maskData: nil)) + fileAttributes.append(.ImageSize(size: size)) + + let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: arc4random64()), partialReference: nil, resource: resource, previewRepresentations: [], mimeType: "image/webp", size: data.count, attributes: fileAttributes) + let message = EnqueueMessage.message(text: "", attributes: [], mediaReference: .standalone(media: media), replyToMessageId: nil, localGroupingKey: nil) + + let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId + strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ + if let strongSelf = self { + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { + $0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil) } + }) + } + }) + strongSelf.sendMessages([message].map { $0.withUpdatedReplyToMessageId(replyMessageId) }) + } + })) + } + private func enqueueChatContextResult(_ results: ChatContextResultCollection, _ result: ChatContextResult) { guard case let .peer(peerId) = self.chatLocation else { return @@ -3989,11 +4042,9 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID strongSelf.recorderFeedback?.error() strongSelf.recorderFeedback = nil } else { - var randomId: Int64 = 0 - arc4random_buf(&randomId, 8) + let randomId = arc4random64() let resource = LocalFileMediaResource(fileId: randomId) - strongSelf.account.postbox.mediaBox.storeResourceData(resource.id, data: data.compressedData) var waveformBuffer: MemoryBuffer? @@ -4080,10 +4131,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID } }) - var randomId: Int64 = 0 - arc4random_buf(&randomId, 8) - - self.sendMessages([.message(text: "", attributes: [], mediaReference: .standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: randomId), partialReference: nil, resource: recordedMediaPreview.resource, previewRepresentations: [], mimeType: "audio/ogg", size: Int(recordedMediaPreview.fileSize), attributes: [.Audio(isVoice: true, duration: Int(recordedMediaPreview.duration), title: nil, performer: nil, waveform: waveformBuffer)])), replyToMessageId: self.presentationInterfaceState.interfaceState.replyMessageId, localGroupingKey: nil)]) + self.sendMessages([.message(text: "", attributes: [], mediaReference: .standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: arc4random64()), partialReference: nil, resource: recordedMediaPreview.resource, previewRepresentations: [], mimeType: "audio/ogg", size: Int(recordedMediaPreview.fileSize), attributes: [.Audio(isVoice: true, duration: Int(recordedMediaPreview.duration), title: nil, performer: nil, waveform: waveformBuffer)])), replyToMessageId: self.presentationInterfaceState.interfaceState.replyMessageId, localGroupingKey: nil)]) } } @@ -5190,8 +5238,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID let images = imageItems as! [UIImage] strongSelf.chatDisplayNode.updateDropInteraction(isActive: false) - - strongSelf.chatDisplayNode.displayPasteMenu(images) + strongSelf.displayPasteMenu(images) } } diff --git a/TelegramUI/ChatControllerInteraction.swift b/TelegramUI/ChatControllerInteraction.swift index 87ebd2ab7e..023fe2dfeb 100644 --- a/TelegramUI/ChatControllerInteraction.swift +++ b/TelegramUI/ChatControllerInteraction.swift @@ -75,6 +75,7 @@ public final class ChatControllerInteraction { let canSetupReply: (Message) -> Bool let navigateToFirstDateMessage: (Int32) -> Void let requestRedeliveryOfFailedMessages: (MessageId) -> Void + let addContact: (String) -> Void let requestMessageUpdate: (MessageId) -> Void let cancelInteractiveKeyboardGestures: () -> Void @@ -85,7 +86,7 @@ public final class ChatControllerInteraction { var contextHighlightedState: ChatInterfaceHighlightedState? var automaticMediaDownloadSettings: AutomaticMediaDownloadSettings - init(openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void, openPeerMention: @escaping (String) -> Void, openMessageContextMenu: @escaping (Message, ASDisplayNode, CGRect) -> Void, navigateToMessage: @escaping (MessageId, MessageId) -> Void, clickThroughMessage: @escaping () -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, sendMessage: @escaping (String) -> Void, sendSticker: @escaping (FileMediaReference, Bool) -> Void, sendGif: @escaping (FileMediaReference) -> Void, requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool) -> Void, activateSwitchInline: @escaping (PeerId?, String) -> Void, openUrl: @escaping (String, Bool, Bool?) -> Void, shareCurrentLocation: @escaping () -> Void, shareAccountContact: @escaping () -> Void, sendBotCommand: @escaping (MessageId?, String) -> Void, openInstantPage: @escaping (Message) -> Void, openHashtag: @escaping (String?, String) -> Void, updateInputState: @escaping ((ChatTextInputState) -> ChatTextInputState) -> Void, updateInputMode: @escaping ((ChatInputMode) -> ChatInputMode) -> Void, openMessageShareMenu: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, navigationController: @escaping () -> NavigationController?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, callPeer: @escaping (PeerId) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction) -> Void, openCheckoutOrReceipt: @escaping (MessageId) -> Void, openSearch: @escaping () -> Void, setupReply: @escaping (MessageId) -> Void, canSetupReply: @escaping (Message) -> Bool, navigateToFirstDateMessage: @escaping(Int32) ->Void, requestRedeliveryOfFailedMessages: @escaping (MessageId) -> Void, requestMessageUpdate: @escaping (MessageId) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, automaticMediaDownloadSettings: AutomaticMediaDownloadSettings) { + init(openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void, openPeerMention: @escaping (String) -> Void, openMessageContextMenu: @escaping (Message, ASDisplayNode, CGRect) -> Void, navigateToMessage: @escaping (MessageId, MessageId) -> Void, clickThroughMessage: @escaping () -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, sendMessage: @escaping (String) -> Void, sendSticker: @escaping (FileMediaReference, Bool) -> Void, sendGif: @escaping (FileMediaReference) -> Void, requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool) -> Void, activateSwitchInline: @escaping (PeerId?, String) -> Void, openUrl: @escaping (String, Bool, Bool?) -> Void, shareCurrentLocation: @escaping () -> Void, shareAccountContact: @escaping () -> Void, sendBotCommand: @escaping (MessageId?, String) -> Void, openInstantPage: @escaping (Message) -> Void, openHashtag: @escaping (String?, String) -> Void, updateInputState: @escaping ((ChatTextInputState) -> ChatTextInputState) -> Void, updateInputMode: @escaping ((ChatInputMode) -> ChatInputMode) -> Void, openMessageShareMenu: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, navigationController: @escaping () -> NavigationController?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, callPeer: @escaping (PeerId) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction) -> Void, openCheckoutOrReceipt: @escaping (MessageId) -> Void, openSearch: @escaping () -> Void, setupReply: @escaping (MessageId) -> Void, canSetupReply: @escaping (Message) -> Bool, navigateToFirstDateMessage: @escaping(Int32) ->Void, requestRedeliveryOfFailedMessages: @escaping (MessageId) -> Void, addContact: @escaping (String) -> Void, requestMessageUpdate: @escaping (MessageId) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, automaticMediaDownloadSettings: AutomaticMediaDownloadSettings) { self.openMessage = openMessage self.openPeer = openPeer self.openPeerMention = openPeerMention @@ -118,6 +119,7 @@ public final class ChatControllerInteraction { self.canSetupReply = canSetupReply self.navigateToFirstDateMessage = navigateToFirstDateMessage self.requestRedeliveryOfFailedMessages = requestRedeliveryOfFailedMessages + self.addContact = addContact self.requestMessageUpdate = requestMessageUpdate self.cancelInteractiveKeyboardGestures = cancelInteractiveKeyboardGestures diff --git a/TelegramUI/ChatControllerNode.swift b/TelegramUI/ChatControllerNode.swift index 326cab01b5..6932df0283 100644 --- a/TelegramUI/ChatControllerNode.swift +++ b/TelegramUI/ChatControllerNode.swift @@ -125,8 +125,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { var requestUpdateInterfaceState: (ContainedViewLayoutTransition, Bool, (ChatPresentationInterfaceState) -> ChatPresentationInterfaceState) -> Void = { _, _, _ in } var sendMessages: ([EnqueueMessage]) -> Void = { _ in } var displayAttachmentMenu: () -> Void = { } - var displayPasteMenu: ([UIImage]) -> Void = { _ in } - var sendGif: (Data) -> Void = { _ in } + var paste: (ChatTextInputPanelPasteData) -> Void = { _ in } var updateTypingActivity: (Bool) -> Void = { _ in } var dismissUrlPreview: () -> Void = { } var setupSendActionOnViewUpdate: (@escaping () -> Void) -> Void = { _ in } @@ -319,11 +318,8 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { } } - self.textInputPanelNode?.pasteImages = { [weak self] images in - self?.displayPasteMenu(images) - } - self.textInputPanelNode?.pasteData = { [weak self] data in - self?.sendGif(data) + self.textInputPanelNode?.paste = { [weak self] data in + self?.paste(data) } self.textInputPanelNode?.displayAttachmentMenu = { [weak self] in self?.displayAttachmentMenu() diff --git a/TelegramUI/ChatInterfaceStateContextMenus.swift b/TelegramUI/ChatInterfaceStateContextMenus.swift index 580cd70f7a..6b4cd88aef 100644 --- a/TelegramUI/ChatInterfaceStateContextMenus.swift +++ b/TelegramUI/ChatInterfaceStateContextMenus.swift @@ -167,7 +167,7 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: loadStickerSaveStatus = file.fileId } } - } else if let _ = media as? TelegramMediaAction { + } else if media is TelegramMediaAction || media is TelegramMediaExpiredContent { isAction = true } else if let image = media as? TelegramMediaImage { if !messages[0].containsSecretMedia { @@ -545,7 +545,7 @@ func chatAvailableMessageActions(postbox: Postbox, accountPeerId: PeerId, messag } else if let peer = transaction.getPeer(id.peerId) { var isAction = false for media in message.media { - if media is TelegramMediaAction { + if media is TelegramMediaAction || media is TelegramMediaExpiredContent { isAction = true } } diff --git a/TelegramUI/ChatItemGalleryFooterContentNode.swift b/TelegramUI/ChatItemGalleryFooterContentNode.swift index 881f0df08f..dbfaa0f6cd 100644 --- a/TelegramUI/ChatItemGalleryFooterContentNode.swift +++ b/TelegramUI/ChatItemGalleryFooterContentNode.swift @@ -89,6 +89,19 @@ enum ChatItemGalleryFooterContent: Equatable { } } +enum ChatItemGalleryFooterContentTapAction { + case none + case url(url: String, concealed: Bool) + case textMention(String) + case peerMention(PeerId, String) + case botCommand(String) + case hashtag(String?, String) + case instantPage + case call(PeerId) + case openMessage + case ignore +} + final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode { private let account: Account private var theme: PresentationTheme @@ -239,6 +252,23 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode { } } self.textNode.tapAttributeAction = { [weak self] attributes in +// if let url = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.URL)] as? String { +// var concealed = true +// if let attributeText = self.textNode.attributeSubstring(name: TelegramTextAttributes.URL, index: index) { +// concealed = !doesUrlMatchText(url: url, text: attributeText) +// } +// return .url(url: url, concealed: concealed) +// } else if let peerMention = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention { +// return .peerMention(peerMention.peerId, peerMention.mention) +// } else if let peerName = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.PeerTextMention)] as? String { +// return .textMention(peerName) +// } else if let botCommand = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.BotCommand)] as? String { +// return .botCommand(botCommand) +// } else if let hashtag = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.Hashtag)] as? TelegramHashtag { +// return .hashtag(hashtag.peerName, hashtag.hashtag) +// } else { +// return .none +// } if let strongSelf = self, let url = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.URL)] as? String { strongSelf.openUrl?(url) } @@ -293,6 +323,8 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode { if self.currentMessageText != caption || self.currentAuthorNameText != titleText || self.currentDateText != dateText { self.currentMessageText = caption + self.currentAuthorNameText = titleText + self.currentDateText = dateText if caption.length == 0 { self.textNode.isHidden = true diff --git a/TelegramUI/ChatListController.swift b/TelegramUI/ChatListController.swift index 20d46ed32b..75680aeb34 100644 --- a/TelegramUI/ChatListController.swift +++ b/TelegramUI/ChatListController.swift @@ -55,6 +55,9 @@ public class ChatListController: TelegramController, KeyShortcutResponder, UIVie private var passcodeLockTooltipDisposable = MetaDisposable() private var didShowPasscodeLockTooltipController = false + private var suggestLocalizationDisposable = MetaDisposable() + private var didSuggestLocalization = false + private var presentationData: PresentationData private var presentationDataDisposable: Disposable? @@ -245,6 +248,7 @@ public class ChatListController: TelegramController, KeyShortcutResponder, UIVie self.badgeDisposable?.dispose() self.badgeIconDisposable?.dispose() self.passcodeLockTooltipDisposable.dispose() + self.suggestLocalizationDisposable.dispose() self.presentationDataDisposable?.dispose() } @@ -308,9 +312,12 @@ public class ChatListController: TelegramController, KeyShortcutResponder, UIVie var items: [ActionSheetItem] = [] var canClear = true var canStop = false + + var deleteTitle = strongSelf.presentationData.strings.Common_Delete if let channel = peer as? TelegramChannel { if case .broadcast = channel.info { canClear = false + deleteTitle = strongSelf.presentationData.strings.Channel_LeaveChannel } if let addressName = channel.addressName, !addressName.isEmpty { canClear = false @@ -328,7 +335,7 @@ public class ChatListController: TelegramController, KeyShortcutResponder, UIVie })) } - items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Delete, color: .destructive, action: { [weak actionSheet] in + items.append(ActionSheetButtonItem(title: deleteTitle, color: .destructive, action: { [weak actionSheet] in actionSheet?.dismissAnimated() if let strongSelf = self { @@ -468,6 +475,17 @@ public class ChatListController: TelegramController, KeyShortcutResponder, UIVie } } + self.chatListDisplayNode.requestAddContact = { [weak self] phoneNumber in + if let strongSelf = self { + strongSelf.chatListDisplayNode.view.endEditing(true) + openAddContact(account: strongSelf.account, phoneNumber: phoneNumber, present: { [weak self] controller, arguments in + self?.present(controller, in: .window(.root), with: arguments) + }, completed: { + self?.deactivateSearch(animated: false) + }) + } + } + /*self.badgeIconDisposable = (self.chatListDisplayNode.chatListNode.scrollToTopOption |> distinctUntilChanged |> deliverOnMainQueue).start(next: { [weak self] option in @@ -530,6 +548,49 @@ public class ChatListController: TelegramController, KeyShortcutResponder, UIVie } })) } + + if !self.didSuggestLocalization { + self.didSuggestLocalization = true + + let network = self.account.network + let signal = self.account.postbox.transaction { transaction -> (String, SuggestedLocalizationEntry?) in + let languageCode: String + if let current = transaction.getPreferencesEntry(key: PreferencesKeys.localizationSettings) as? LocalizationSettings { + languageCode = current.primaryComponent.languageCode + } else { + languageCode = "en" + } + var suggestedLocalization: SuggestedLocalizationEntry? + if let localization = transaction.getPreferencesEntry(key: PreferencesKeys.suggestedLocalization) as? SuggestedLocalizationEntry { + suggestedLocalization = localization + } + return (languageCode, suggestedLocalization) + } |> mapToSignal({ value -> Signal<(String, SuggestedLocalizationInfo)?, NoError> in + guard let suggestedLocalization = value.1, !suggestedLocalization.isSeen && suggestedLocalization.languageCode != "en" && suggestedLocalization.languageCode != value.0 else { + return .single(nil) + } + return suggestedLocalizationInfo(network: network, languageCode: suggestedLocalization.languageCode, extractKeys: LanguageSuggestionControllerStrings.keys) + |> map({ suggestedLocalization -> (String, SuggestedLocalizationInfo)? in + return (value.0, suggestedLocalization) + }) + }) + + self.suggestLocalizationDisposable.set((signal |> deliverOnMainQueue).start(next: { [weak self] suggestedLocalization in + guard let strongSelf = self, let (currentLanguageCode, suggestedLocalization) = suggestedLocalization else { + return + } + if let controller = languageSuggestionController(account: strongSelf.account, suggestedLocalization: suggestedLocalization, currentLanguageCode: currentLanguageCode, openSelection: { [weak self] in + if let strongSelf = self { + let controller = LanguageSelectionController(account: strongSelf.account) + (strongSelf.navigationController as? NavigationController)?.replaceAllButRootController(controller, animated: true) + } + }) { + strongSelf.present(controller, in: .window(.root)) + + //_ = markSuggestedLocalizationAsSeenInteractively(postbox: strongSelf.account.postbox, languageCode: suggestedLocalization.languageCode).start() + } + })) + } } override public func viewDidDisappear(_ animated: Bool) { diff --git a/TelegramUI/ChatListControllerNode.swift b/TelegramUI/ChatListControllerNode.swift index b394b3522e..fca3ec36d5 100644 --- a/TelegramUI/ChatListControllerNode.swift +++ b/TelegramUI/ChatListControllerNode.swift @@ -20,6 +20,7 @@ class ChatListControllerNode: ASDisplayNode { var requestOpenPeerFromSearch: ((Peer, Bool) -> Void)? var requestOpenRecentPeerOptions: ((Peer) -> Void)? var requestOpenMessageFromSearch: ((Peer, MessageId) -> Void)? + var requestAddContact: ((String) -> Void)? var themeAndStrings: (PresentationTheme, PresentationStrings, dateTimeFormat: PresentationDateTimeFormat) @@ -145,6 +146,10 @@ class ChatListControllerNode: ASDisplayNode { if let requestOpenMessageFromSearch = self?.requestOpenMessageFromSearch { requestOpenMessageFromSearch(peer, messageId) } + }, addContact: { [weak self] phoneNumber in + if let requestAddContact = self?.requestAddContact { + requestAddContact(phoneNumber) + } }), cancel: { [weak self] in if let requestDeactivateSearch = self?.requestDeactivateSearch { requestDeactivateSearch() diff --git a/TelegramUI/ChatListNode.swift b/TelegramUI/ChatListNode.swift index c70d6e6998..74a2351ec5 100644 --- a/TelegramUI/ChatListNode.swift +++ b/TelegramUI/ChatListNode.swift @@ -54,6 +54,7 @@ final class ChatListNodeInteraction { let peerSelected: (Peer) -> Void let messageSelected: (Message, Bool) -> Void let groupSelected: (PeerGroupId) -> Void + let addContact: (String) -> Void let setPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void let setItemPinned: (PinnedItemId, Bool) -> Void let setPeerMuted: (PeerId, Bool) -> Void @@ -63,11 +64,12 @@ final class ChatListNodeInteraction { var highlightedChatLocation: ChatListHighlightedLocation? - init(activateSearch: @escaping () -> Void, peerSelected: @escaping (Peer) -> Void, messageSelected: @escaping (Message, Bool) -> Void, groupSelected: @escaping (PeerGroupId) -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, setItemPinned: @escaping (PinnedItemId, Bool) -> Void, setPeerMuted: @escaping (PeerId, Bool) -> Void, deletePeer: @escaping (PeerId) -> Void, updatePeerGrouping: @escaping (PeerId, Bool) -> Void, togglePeerMarkedUnread: @escaping (PeerId, Bool) -> Void) { + init(activateSearch: @escaping () -> Void, peerSelected: @escaping (Peer) -> Void, messageSelected: @escaping (Message, Bool) -> Void, groupSelected: @escaping (PeerGroupId) -> Void, addContact: @escaping (String) -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, setItemPinned: @escaping (PinnedItemId, Bool) -> Void, setPeerMuted: @escaping (PeerId, Bool) -> Void, deletePeer: @escaping (PeerId) -> Void, updatePeerGrouping: @escaping (PeerId, Bool) -> Void, togglePeerMarkedUnread: @escaping (PeerId, Bool) -> Void) { self.activateSearch = activateSearch self.peerSelected = peerSelected self.messageSelected = messageSelected self.groupSelected = groupSelected + self.addContact = addContact self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions self.setItemPinned = setItemPinned self.setPeerMuted = setPeerMuted @@ -293,6 +295,7 @@ final class ChatListNode: ListView { var peerSelected: ((PeerId, Bool, Bool) -> Void)? var groupSelected: ((PeerGroupId) -> Void)? + var addContact: ((String) -> Void)? var activateSearch: (() -> Void)? var deletePeerChat: ((PeerId) -> Void)? var updatePeerGrouping: ((PeerId, Bool) -> Void)? @@ -371,6 +374,7 @@ final class ChatListNode: ListView { if let strongSelf = self, let groupSelected = strongSelf.groupSelected { groupSelected(groupId) } + }, addContact: { _ in }, setPeerIdWithRevealedOptions: { [weak self] peerId, fromPeerId in if let strongSelf = self { strongSelf.updateState { state in diff --git a/TelegramUI/ChatListSearchContainerNode.swift b/TelegramUI/ChatListSearchContainerNode.swift index 7551e10d7a..5fd3da1e28 100644 --- a/TelegramUI/ChatListSearchContainerNode.swift +++ b/TelegramUI/ChatListSearchContainerNode.swift @@ -196,6 +196,7 @@ enum ChatListSearchEntryStableId: Hashable { case localPeerId(PeerId) case globalPeerId(PeerId) case messageId(MessageId) + case addContact static func ==(lhs: ChatListSearchEntryStableId, rhs: ChatListSearchEntryStableId) -> Bool { switch lhs { @@ -217,6 +218,12 @@ enum ChatListSearchEntryStableId: Hashable { } else { return false } + case .addContact: + if case .addContact = rhs { + return true + } else { + return false + } } } @@ -228,6 +235,8 @@ enum ChatListSearchEntryStableId: Hashable { return peerId.hashValue case let .messageId(messageId): return messageId.hashValue + case .addContact: + return 0 } } } @@ -237,6 +246,7 @@ enum ChatListSearchEntry: Comparable, Identifiable { case localPeer(Peer, Peer?, UnreadSearchBadge?, Int, PresentationTheme, PresentationStrings, PresentationPersonNameOrder, PresentationPersonNameOrder) case globalPeer(FoundPeer, UnreadSearchBadge?, Int, PresentationTheme, PresentationStrings, PresentationPersonNameOrder, PresentationPersonNameOrder) case message(Message, CombinedPeerReadState?, ChatListPresentationData) + case addContact(String, PresentationTheme, PresentationStrings) var stableId: ChatListSearchEntryStableId { switch self { @@ -246,6 +256,8 @@ enum ChatListSearchEntry: Comparable, Identifiable { return .globalPeerId(peer.peer.id) case let .message(message, _, _): return .messageId(message.id) + case .addContact: + return .addContact } } @@ -281,6 +293,21 @@ enum ChatListSearchEntry: Comparable, Identifiable { } else { return false } + case let .addContact(lhsPhoneNumber, lhsTheme, lhsStrings): + if case let .addContact(rhsPhoneNumber, rhsTheme, rhsStrings) = rhs { + if lhsPhoneNumber != rhsPhoneNumber { + return false + } + if lhsTheme !== rhsTheme { + return false + } + if lhsStrings !== rhsStrings { + return false + } + return true + } else { + return false + } } } @@ -298,15 +325,19 @@ enum ChatListSearchEntry: Comparable, Identifiable { return false case let .globalPeer(_, _, rhsIndex, _, _, _, _): return lhsIndex <= rhsIndex - case .message: + case .message, .addContact: return true } case let .message(lhsMessage, _, _): if case let .message(rhsMessage, _, _) = rhs { return MessageIndex(lhsMessage) < MessageIndex(rhsMessage) + } else if case .addContact = rhs { + return true } else { return false } + case .addContact: + return false } } @@ -399,6 +430,10 @@ enum ChatListSearchEntry: Comparable, Identifiable { }) case let .message(message, readState, presentationData): return ChatListItem(presentationData: presentationData, account: account, peerGroupId: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(message)), content: .peer(message: message, peer: RenderedPeer(message: message), combinedReadState: readState, notificationSettings: nil, summaryInfo: ChatListMessageTagSummaryInfo(), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: true), editing: false, hasActiveRevealControls: false, header: enableHeaders ? ChatListSearchItemHeader(type: .messages, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil) : nil, enableContextActions: false, interaction: interaction) + case let .addContact(phoneNumber, theme, strings): + return ContactsAddItem(theme: theme, strings: strings, phoneNumber: phoneNumber, header: ChatListSearchItemHeader(type: .phoneNumber, theme: theme, strings: strings, actionTitle: nil, action: nil), action: { + interaction.addContact(phoneNumber) + }) } } } @@ -475,7 +510,6 @@ private func doesPeerMatchFilter(peer: Peer, filter: ChatListNodePeersFilter) -> final class ChatListSearchContainerNode: SearchDisplayControllerContentNode { private let account: Account - private let openMessage: (Peer, MessageId) -> Void private let recentListNode: ListView private let listNode: ListView @@ -502,9 +536,8 @@ final class ChatListSearchContainerNode: SearchDisplayControllerContentNode { return self._isSearching.get() } - init(account: Account, filter: ChatListNodePeersFilter, groupId: PeerGroupId?, openPeer: @escaping (Peer, Bool) -> Void, openRecentPeerOptions: @escaping (Peer) -> Void, openMessage: @escaping (Peer, MessageId) -> Void) { + init(account: Account, filter: ChatListNodePeersFilter, groupId: PeerGroupId?, openPeer: @escaping (Peer, Bool) -> Void, openRecentPeerOptions: @escaping (Peer) -> Void, openMessage: @escaping (Peer, MessageId) -> Void, addContact: ((String) -> Void)?) { self.account = account - self.openMessage = openMessage self.presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } self.presentationDataPromise = Promise(ChatListPresentationData(theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: self.presentationData.disableAnimations)) @@ -630,6 +663,10 @@ final class ChatListSearchContainerNode: SearchDisplayControllerContentNode { } } + if addContact != nil && isViablePhoneNumber(query) { + entries.append(.addContact(query, presentationData.theme, presentationData.strings)) + } + return (entries, isSearching) } } else { @@ -650,6 +687,9 @@ final class ChatListSearchContainerNode: SearchDisplayControllerContentNode { } self?.listNode.clearHighlightAnimated(true) }, groupSelected: { _ in + }, addContact: { [weak self] phoneNumber in + addContact?(phoneNumber) + self?.listNode.clearHighlightAnimated(true) }, setPeerIdWithRevealedOptions: { [weak self] peerId, fromPeerId in if let strongSelf = self { strongSelf.updateState { state in diff --git a/TelegramUI/ChatListSearchItemHeader.swift b/TelegramUI/ChatListSearchItemHeader.swift index fcbcffcf62..eda8d78f33 100644 --- a/TelegramUI/ChatListSearchItemHeader.swift +++ b/TelegramUI/ChatListSearchItemHeader.swift @@ -9,6 +9,7 @@ enum ChatListSearchItemHeaderType: Int32 { case deviceContacts case recentPeers case messages + case phoneNumber } final class ChatListSearchItemHeader: ListViewItemHeader { @@ -71,6 +72,8 @@ final class ChatListSearchItemHeaderNode: ListViewItemHeaderNode { self.sectionHeaderNode.title = strings.DialogList_SearchSectionMessages.uppercased() case .recentPeers: self.sectionHeaderNode.title = strings.DialogList_SearchSectionRecent.uppercased() + case .phoneNumber: + self.sectionHeaderNode.title = strings.Contacts_PhoneNumber.uppercased() } self.sectionHeaderNode.action = actionTitle diff --git a/TelegramUI/ChatMessageActionSheetControllerNode.swift b/TelegramUI/ChatMessageActionSheetControllerNode.swift index f58b134787..49cb697d17 100644 --- a/TelegramUI/ChatMessageActionSheetControllerNode.swift +++ b/TelegramUI/ChatMessageActionSheetControllerNode.swift @@ -205,7 +205,7 @@ final class ChatMessageActionSheetControllerNode: ViewControllerTracingNode { } } if let associatedController = self.associatedController { - let subpoint = self.view.convert(point, to: nil) // temporary fix of iPad landscape+keyboard issue. Point converts to portrait coordinate system otherwise + let subpoint = self.view.convert(point, to: nil) if let result = associatedController.view.hitTest(subpoint, with: event) { return result } diff --git a/TelegramUI/ChatMessageBubbleItemNode.swift b/TelegramUI/ChatMessageBubbleItemNode.swift index b8f63d3737..20dfb177f9 100644 --- a/TelegramUI/ChatMessageBubbleItemNode.swift +++ b/TelegramUI/ChatMessageBubbleItemNode.swift @@ -87,13 +87,7 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> [( return result } -private let nameFont: UIFont = { - if #available(iOS 8.2, *) { - return UIFont.systemFont(ofSize: 14.0, weight: UIFont.Weight.medium) - } else { - return CTFontCreateWithName("HelveticaNeue-Medium" as CFString, 14.0, nil) - } -}() +private let nameFont = Font.medium(14.0) private let inlineBotPrefixFont = Font.regular(14.0) private let inlineBotNameFont = nameFont diff --git a/TelegramUI/ChatMessageReplyInfoNode.swift b/TelegramUI/ChatMessageReplyInfoNode.swift index 6882b7f188..32a82af8ae 100644 --- a/TelegramUI/ChatMessageReplyInfoNode.swift +++ b/TelegramUI/ChatMessageReplyInfoNode.swift @@ -5,13 +5,7 @@ import Display import TelegramCore import SwiftSignalKit -private let titleFont: UIFont = { - if #available(iOS 8.2, *) { - return UIFont.systemFont(ofSize: 14.0, weight: UIFont.Weight.medium) - } else { - return CTFontCreateWithName("HelveticaNeue-Medium" as CFString, 14.0, nil) - } -}() +private let titleFont = Font.medium(14.0) private let textFont = Font.regular(14.0) enum ChatMessageReplyInfoType { @@ -25,7 +19,6 @@ class ChatMessageReplyInfoNode: ASDisplayNode { private var titleNode: TextNode? private var textNode: TextNode? private var imageNode: TransformImageNode? - private var overlayIconNode: ASImageNode? private var previousMediaReference: AnyMediaReference? override init() { @@ -79,8 +72,6 @@ class ChatMessageReplyInfoNode: ASDisplayNode { var leftInset: CGFloat = 11.0 let spacing: CGFloat = 2.0 - var overlayIcon: UIImage? - var updatedMediaReference: AnyMediaReference? var imageDimensions: CGSize? var hasRoundImage = false @@ -100,9 +91,6 @@ class ChatMessageReplyInfoNode: ASDisplayNode { } else if let representation = largestImageRepresentation(file.previewRepresentations), !file.isSticker { imageDimensions = representation.dimensions } - if !file.isInstantVideo && !file.isAnimated { - overlayIcon = PresentationResourcesChat.chatBubbleReplyThumbnailPlayImage(theme) - } if file.isInstantVideo { hasRoundImage = true } @@ -179,7 +167,6 @@ class ChatMessageReplyInfoNode: ASDisplayNode { node.contentNode.addSubnode(textNode) } - var imageFrame: CGRect? if let applyImage = applyImage { let imageNode = applyImage() if node.imageNode == nil { @@ -188,7 +175,6 @@ class ChatMessageReplyInfoNode: ASDisplayNode { node.imageNode = imageNode } imageNode.frame = CGRect(origin: CGPoint(x: 8.0, y: 4.0 + UIScreenPixel), size: CGSize(width: 30.0, height: 30.0)) - imageFrame = imageNode.frame if let updateImageSignal = updateImageSignal { imageNode.setSignal(updateImageSignal) @@ -198,25 +184,6 @@ class ChatMessageReplyInfoNode: ASDisplayNode { node.imageNode = nil } - if let overlayIcon = overlayIcon, let imageFrame = imageFrame { - let overlayIconNode: ASImageNode - if let current = node.overlayIconNode { - overlayIconNode = current - } else { - overlayIconNode = ASImageNode() - overlayIconNode.isLayerBacked = true - overlayIconNode.displayWithoutProcessing = true - overlayIconNode.displaysAsynchronously = false - node.overlayIconNode = overlayIconNode - node.addSubnode(overlayIconNode) - } - overlayIconNode.image = overlayIcon - overlayIconNode.frame = CGRect(origin: CGPoint(x: imageFrame.minX + floor((imageFrame.size.width - overlayIcon.size.width) / 2.0), y: imageFrame.minY + floor((imageFrame.size.height - overlayIcon.size.height) / 2.0)), size: overlayIcon.size) - } else if let overlayIconNode = node.overlayIconNode { - overlayIconNode.removeFromSupernode() - node.overlayIconNode = nil - } - titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: spacing), size: titleLayout.size) textNode.frame = CGRect(origin: CGPoint(x: leftInset, y: titleNode.frame.maxY + spacing), size: textLayout.size) diff --git a/TelegramUI/ChatMessageStickerItemNode.swift b/TelegramUI/ChatMessageStickerItemNode.swift index c05eb4bfc1..0b9acec096 100644 --- a/TelegramUI/ChatMessageStickerItemNode.swift +++ b/TelegramUI/ChatMessageStickerItemNode.swift @@ -191,7 +191,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { let imageFrame = CGRect(origin: CGPoint(x: 0.0 + (incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + avatarInset + layoutConstants.bubble.contentInsets.left) : (params.width - params.rightInset - imageSize.width - layoutConstants.bubble.edgeInset - layoutConstants.bubble.contentInsets.left)), y: 0.0), size: CGSize(width: imageSize.width + 0.0, height: imageSize.height + 0.0)) - let arguments = TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: CGSize(width: imageSize.width + 0.0, height: imageSize.height + 0.0), intrinsicInsets: UIEdgeInsets(top: 10.0, left: 10.0, bottom: 10.0, right: 10.0)) + let arguments = TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: CGSize(width: imageSize.width, height: imageSize.height), intrinsicInsets: UIEdgeInsets(top: 10.0, left: 10.0, bottom: 10.0, right: 10.0)) let imageApply = imageLayout(arguments) diff --git a/TelegramUI/ChatMessageWebpageBubbleContentNode.swift b/TelegramUI/ChatMessageWebpageBubbleContentNode.swift index e4b9b87c37..9ce4ef6548 100644 --- a/TelegramUI/ChatMessageWebpageBubbleContentNode.swift +++ b/TelegramUI/ChatMessageWebpageBubbleContentNode.swift @@ -57,12 +57,12 @@ func instantPageGalleryMedia(webpageId: MediaId, page: InstantPage, galleryMedia } if !found { - 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) + result.insert(InstantPageGalleryEntry(index: Int32(counter), pageId: webpageId, media: InstantPageMedia(index: counter, media: galleryMedia, url: nil, caption: nil, credit: nil), caption: nil, credit: nil, location: InstantPageGalleryEntryLocation(position: Int32(counter), totalCount: 0)), at: 0) } for i in 0 ..< result.count { let item = result[i] - result[i] = InstantPageGalleryEntry(index: Int32(i), pageId: item.pageId, media: item.media, caption: item.caption, location: InstantPageGalleryEntryLocation(position: Int32(i), totalCount: Int32(result.count))) + result[i] = InstantPageGalleryEntry(index: Int32(i), pageId: item.pageId, media: item.media, caption: item.caption, credit: item.credit, location: InstantPageGalleryEntryLocation(position: Int32(i), totalCount: Int32(result.count))) } return result } @@ -71,13 +71,13 @@ private func instantPageBlockMedia(pageId: MediaId, block: InstantPageBlock, med switch block { case let .image(id, caption, _, _): if let m = media[id] { - 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))] + let result = [InstantPageGalleryEntry(index: Int32(counter), pageId: pageId, media: InstantPageMedia(index: counter, media: m, url: nil, caption: caption.text, credit: caption.credit), caption: caption.text, credit: caption.credit, location: InstantPageGalleryEntryLocation(position: Int32(counter), totalCount: 0))] counter += 1 return result } - case let .video(id, caption, _, loop): + case let .video(id, caption, _, _): if let m = media[id] { - 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))] + let result = [InstantPageGalleryEntry(index: Int32(counter), pageId: pageId, media: InstantPageMedia(index: counter, media: m, url: nil, caption: caption.text, credit: caption.credit), caption: caption.text, credit: caption.credit, location: InstantPageGalleryEntryLocation(position: Int32(counter), totalCount: 0))] counter += 1 return result } diff --git a/TelegramUI/ChatRecentActionsControllerNode.swift b/TelegramUI/ChatRecentActionsControllerNode.swift index 393a61d596..82eac00b4a 100644 --- a/TelegramUI/ChatRecentActionsControllerNode.swift +++ b/TelegramUI/ChatRecentActionsControllerNode.swift @@ -349,6 +349,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { }, navigateToFirstDateMessage: { _ in }, requestRedeliveryOfFailedMessages: { _ in + }, addContact: { _ in }, requestMessageUpdate: { _ in }, cancelInteractiveKeyboardGestures: { }, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings) diff --git a/TelegramUI/ChatTextInputPanelNode.swift b/TelegramUI/ChatTextInputPanelNode.swift index 872a172b34..de46db6d3b 100644 --- a/TelegramUI/ChatTextInputPanelNode.swift +++ b/TelegramUI/ChatTextInputPanelNode.swift @@ -163,6 +163,12 @@ private func textInputBackgroundImage(backgroundColor: UIColor, strokeColor: UIC } } +enum ChatTextInputPanelPasteData { + case images([UIImage]) + case gif(Data) + case sticker(UIImage) +} + class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { var textPlaceholderNode: TextNode var contextPlaceholderNode: TextNode? @@ -186,8 +192,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { var displayAttachmentMenu: () -> Void = { } var sendMessage: () -> Void = { } - var pasteImages: ([UIImage]) -> Void = { _ in } - var pasteData: (Data) -> Void = { _ in } + var paste: (ChatTextInputPanelPasteData) -> Void = { _ in } var updateHeight: () -> Void = { } var updateActivity: () -> Void = { } @@ -1309,21 +1314,34 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { let pasteboard = UIPasteboard.general var images: [UIImage] = [] - if let gifData = pasteboard.data(forPasteboardType: "com.compuserve.gif") { - self.pasteData(gifData) + if let data = pasteboard.data(forPasteboardType: "com.compuserve.gif") { + self.paste(.gif(data)) return false } else { - for item in UIPasteboard.general.items { - if let image = item[kUTTypeJPEG as String] as? UIImage { + var isPNG = false + for item in pasteboard.items { + if let image = item[kUTTypePNG as String] as? UIImage { images.append(image) - } else if let image = item[kUTTypePNG as String] as? UIImage { + isPNG = true + } else if let image = item["com.apple.uikit.image"] as? UIImage { + images.append(image) + isPNG = true + } else if let image = item[kUTTypeJPEG as String] as? UIImage { images.append(image) } else if let image = item[kUTTypeGIF as String] as? UIImage { images.append(image) } } + + if isPNG && images.count == 1, let image = images.first { + if imageHasTransparency(image) { + self.paste(.sticker(image)) + return false + } + } + if !images.isEmpty { - self.pasteImages(images) + self.paste(.images(images)) return false } } diff --git a/TelegramUI/ContactAddItem.swift b/TelegramUI/ContactAddItem.swift new file mode 100644 index 0000000000..0d57699538 --- /dev/null +++ b/TelegramUI/ContactAddItem.swift @@ -0,0 +1,251 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Postbox +import Display +import SwiftSignalKit +import TelegramCore + +private let titleFont = Font.regular(17.0) + +class ContactsAddItem: ListViewItem { + let theme: PresentationTheme + let strings: PresentationStrings + let phoneNumber: String + let action: () -> Void + + let header: ListViewItemHeader? + + init(theme: PresentationTheme, strings: PresentationStrings, phoneNumber: String, header: ListViewItemHeader?, action: @escaping () -> Void) { + self.theme = theme + self.strings = strings + self.phoneNumber = phoneNumber + self.action = action + self.header = header + } + + func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, () -> Void)) -> Void) { + async { + let node = ContactsAddItemNode() + let makeLayout = node.asyncLayout() + let (first, last, firstWithHeader) = ContactsAddItem.mergeType(item: self, previousItem: previousItem, nextItem: nextItem) + let (nodeLayout, nodeApply) = makeLayout(self, params, first, last, firstWithHeader) + node.contentSize = nodeLayout.contentSize + node.insets = nodeLayout.insets + + completion(node, { + return (nil, { nodeApply(false) }) + }) + } + } + + func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) { + Queue.mainQueue().async { + if let nodeValue = node() as? ContactsAddItemNode { + let layout = nodeValue.asyncLayout() + async { + let (first, last, firstWithHeader) = ContactsAddItem.mergeType(item: self, previousItem: previousItem, nextItem: nextItem) + let (nodeLayout, apply) = layout(self, params, first, last, firstWithHeader) + Queue.mainQueue().async { + completion(nodeLayout, { + apply(animation.isAnimated) + }) + } + } + } + } + } + + var selectable: Bool { + return true + } + + func selected(listView: ListView) { + self.action() + } + + static func mergeType(item: ContactsAddItem, previousItem: ListViewItem?, nextItem: ListViewItem?) -> (first: Bool, last: Bool, firstWithHeader: Bool) { + var first = false + var last = false + var firstWithHeader = false + if let previousItem = previousItem { + if let header = item.header { + if let previousItem = previousItem as? ContactsAddItem { + firstWithHeader = header.id != previousItem.header?.id + } else { + firstWithHeader = true + } + } + } else { + first = true + firstWithHeader = item.header != nil + } + if let nextItem = nextItem { + if let header = item.header { + if let nextItem = nextItem as? ContactsAddItem { + last = header.id != nextItem.header?.id + } else { + last = true + } + } + } else { + last = true + } + return (first, last, firstWithHeader) + } +} + +private let separatorHeight = 1.0 / UIScreen.main.scale + +class ContactsAddItemNode: ListViewItemNode { + private let backgroundNode: ASDisplayNode + private let separatorNode: ASDisplayNode + private let highlightedBackgroundNode: ASDisplayNode + + private let iconNode: ASImageNode + private let titleNode: TextNode + + + private var layoutParams: (ContactsAddItem, ListViewItemLayoutParams, Bool, Bool, Bool)? + private var item: ContactsAddItem? { + return self.layoutParams?.0 + } + + required init() { + self.backgroundNode = ASDisplayNode() + self.backgroundNode.isLayerBacked = true + + self.separatorNode = ASDisplayNode() + self.separatorNode.isLayerBacked = true + + self.highlightedBackgroundNode = ASDisplayNode() + self.highlightedBackgroundNode.isLayerBacked = true + + self.iconNode = ASImageNode() + self.titleNode = TextNode() + + super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + + self.addSubnode(self.backgroundNode) + self.addSubnode(self.separatorNode) + self.addSubnode(self.iconNode) + self.addSubnode(self.titleNode) + } + + override func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) { + if let (item, _, _, _, _) = self.layoutParams { + let (first, last, firstWithHeader) = ContactsAddItem.mergeType(item: item, previousItem: previousItem, nextItem: nextItem) + self.layoutParams = (item, params, first, last, firstWithHeader) + let makeLayout = self.asyncLayout() + let (nodeLayout, nodeApply) = makeLayout(item, params, first, last, firstWithHeader) + self.contentSize = nodeLayout.contentSize + self.insets = nodeLayout.insets + let _ = nodeApply(false) + } + } + + override func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) { + super.setHighlighted(highlighted, at: point, animated: animated) + + if highlighted { + self.highlightedBackgroundNode.alpha = 1.0 + if self.highlightedBackgroundNode.supernode == nil { + self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: self.separatorNode) + } + } else { + if self.highlightedBackgroundNode.supernode != nil { + if animated { + self.highlightedBackgroundNode.layer.animateAlpha(from: self.highlightedBackgroundNode.alpha, to: 0.0, duration: 0.4, completion: { [weak self] completed in + if let strongSelf = self { + if completed { + strongSelf.highlightedBackgroundNode.removeFromSupernode() + } + } + }) + self.highlightedBackgroundNode.alpha = 0.0 + } else { + self.highlightedBackgroundNode.removeFromSupernode() + } + } + } + } + + func asyncLayout() -> (_ item: ContactsAddItem, _ params: ListViewItemLayoutParams, _ first: Bool, _ last: Bool, _ firstWithHeader: Bool) -> (ListViewItemNodeLayout, (Bool) -> Void) { + let makeTitleLayout = TextNode.asyncLayout(self.titleNode) + + let currentItem = self.layoutParams?.0 + + return { [weak self] item, params, first, last, firstWithHeader in + var updatedTheme: PresentationTheme? + var updatedIcon: UIImage? + if currentItem?.theme !== item.theme { + updatedTheme = item.theme + updatedIcon = generateTintedImage(image: UIImage(bundleImageName: "Contact List/AddMemberIcon"), color: item.theme.list.itemAccentColor) + } + let leftInset: CGFloat = 65.0 + params.leftInset + let rightInset: CGFloat = 10.0 + params.rightInset + + let titleAttributedString = NSAttributedString(string: item.strings.Contacts_AddPhoneNumber(formatPhoneNumber(item.phoneNumber)).0, font: titleFont, textColor: item.theme.list.itemAccentColor) + + let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: max(0.0, params.width - leftInset - rightInset), height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + + let nodeLayout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: 48.0), insets: UIEdgeInsets(top: firstWithHeader ? 29.0 : 0.0, left: 0.0, bottom: 0.0, right: 0.0)) + + let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: 13.0), size: titleLayout.size) + + return (nodeLayout, { [weak self] animated in + if let strongSelf = self { + strongSelf.layoutParams = (item, params, first, last, firstWithHeader) + + let transition: ContainedViewLayoutTransition + if animated { + transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring) + } else { + transition = .immediate + } + + if let _ = updatedTheme { + strongSelf.separatorNode.backgroundColor = item.theme.list.itemPlainSeparatorColor + strongSelf.backgroundNode.backgroundColor = item.theme.list.plainBackgroundColor + strongSelf.highlightedBackgroundNode.backgroundColor = item.theme.list.itemHighlightedBackgroundColor + } + + let _ = titleApply() + transition.updateFrame(node: strongSelf.titleNode, frame: titleFrame) + + if let updatedIcon = updatedIcon { + strongSelf.iconNode.image = updatedIcon + } + transition.updateFrame(node: strongSelf.iconNode, frame: CGRect(x: 14.0, y: 4.0, width: 40.0, height: 40.0)) + + let topHighlightInset: CGFloat = (first || !nodeLayout.insets.top.isZero) ? 0.0 : separatorHeight + strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: nodeLayout.contentSize.width, height: nodeLayout.contentSize.height)) + strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -nodeLayout.insets.top - topHighlightInset), size: CGSize(width: nodeLayout.size.width, height: nodeLayout.size.height + topHighlightInset)) + strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: leftInset, y: nodeLayout.contentSize.height - separatorHeight), size: CGSize(width: max(0.0, nodeLayout.size.width - leftInset), height: separatorHeight)) + strongSelf.separatorNode.isHidden = last + } + }) + } + } + + override func layoutHeaderAccessoryItemNode(_ accessoryItemNode: ListViewAccessoryItemNode) { + let bounds = self.bounds + accessoryItemNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -29.0), size: CGSize(width: bounds.size.width, height: 29.0)) + } + + override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) { + self.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration * 0.5) + } + + override func animateRemoved(_ currentTimestamp: Double, duration: Double) { + self.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration * 0.5, removeOnCompletion: false) + } + + override public func header() -> ListViewItemHeader? { + if let (item, _, _, _, _) = self.layoutParams { + return item.header + } else { + return nil + } + } +} diff --git a/TelegramUI/DeviceContactInfoController.swift b/TelegramUI/DeviceContactInfoController.swift index cccd3c1ac9..e5eee13928 100644 --- a/TelegramUI/DeviceContactInfoController.swift +++ b/TelegramUI/DeviceContactInfoController.swift @@ -708,7 +708,7 @@ private final class DeviceContactInfoController: ItemListController Void)? = nil) -> ViewController { +public func deviceContactInfoController(account: Account, subject: DeviceContactInfoSubject, completed: (() -> Void)? = nil, cancelled: (() -> Void)? = nil) -> ViewController { var initialState = DeviceContactInfoState() if case let .create(peer, contactData, _) = subject { var peerPhoneNumber: String? @@ -941,6 +941,7 @@ public func deviceContactInfoController(account: Account, subject: DeviceContact if let composedContactData = composedContactData { let _ = (account.telegramApplicationContext.contactDataManager.createContactWithData(composedContactData) |> deliverOnMainQueue).start(next: { contactIdAndData in + completed?() dismissImpl?(true) }) } diff --git a/TelegramUI/FetchCachedRepresentations.swift b/TelegramUI/FetchCachedRepresentations.swift index 956066968c..21464b15a7 100644 --- a/TelegramUI/FetchCachedRepresentations.swift +++ b/TelegramUI/FetchCachedRepresentations.swift @@ -70,7 +70,7 @@ private func fetchCachedStickerAJpegRepresentation(account: Account, resource: M let colorData = NSMutableData() let alphaData = NSMutableData() - let size = representation.size ?? CGSize(width: image.size.width * image.scale, height: image.size.height * image.scale) + let size = representation.size != nil ? image.size.aspectFitted(representation.size!) : CGSize(width: image.size.width * image.scale, height: image.size.height * image.scale) let colorImage: UIImage if let _ = representation.size { diff --git a/TelegramUI/GalleryControllerNode.swift b/TelegramUI/GalleryControllerNode.swift index 74377ec806..8bd6af7708 100644 --- a/TelegramUI/GalleryControllerNode.swift +++ b/TelegramUI/GalleryControllerNode.swift @@ -96,7 +96,7 @@ class GalleryControllerNode: ASDisplayNode, UIScrollViewDelegate, UIGestureRecog if let (updatedItems, index, progress) = itemsIndexAndProgress { if let (centralId, centralItem) = strongSelf.pager.items[index].thumbnailItem() { var items: [GalleryThumbnailItem] - var indexes: [Int]? + var indexes: [Int] if updatedItems != nil || strongSelf.currentThumbnailContainerNode == nil { items = [centralItem] @@ -104,7 +104,7 @@ class GalleryControllerNode: ASDisplayNode, UIScrollViewDelegate, UIGestureRecog for i in (0 ..< index).reversed() { if let (id, item) = strongSelf.pager.items[i].thumbnailItem(), id == centralId { items.insert(item, at: 0) - indexes?.insert(i, at: 0) + indexes.insert(i, at: 0) } else { break } @@ -112,20 +112,26 @@ class GalleryControllerNode: ASDisplayNode, UIScrollViewDelegate, UIGestureRecog for i in (index + 1) ..< strongSelf.pager.items.count { if let (id, item) = strongSelf.pager.items[i].thumbnailItem(), id == centralId { items.append(item) - indexes?.append(i) + indexes.append(i) } else { break } } } else if let currentThumbnailContainerNode = strongSelf.currentThumbnailContainerNode { items = currentThumbnailContainerNode.items + indexes = currentThumbnailContainerNode.indexes } else { items = [] + indexes = [] assertionFailure() } - if let index = items.index(where: { $0.isEqual(to: centralItem) }) { - let convertedIndex = (index, progress) + var convertedIndex: Int? + if let firstIndex = indexes.first { + convertedIndex = index - firstIndex + } + + if let convertedIndex = convertedIndex { if strongSelf.currentThumbnailContainerNode?.groupId != centralId { if items.count > 1 { node = GalleryThumbnailContainerNode(groupId: centralId) @@ -133,13 +139,11 @@ class GalleryControllerNode: ASDisplayNode, UIScrollViewDelegate, UIGestureRecog } else { node = strongSelf.currentThumbnailContainerNode } - node?.updateItems(items, centralIndex: convertedIndex.0, progress: convertedIndex.1) - if let indexes = indexes { - node?.itemChanged = { [weak self] index in - if let strongSelf = self { - let pagerIndex = indexes[index] - strongSelf.pager.transaction(GalleryPagerTransaction(deleteItems: [], insertItems: [], updateItems: [], focusOnItem: pagerIndex)) - } + node?.updateItems(items, indexes: indexes, centralIndex: convertedIndex, progress: progress) + node?.itemChanged = { [weak self] index in + if let strongSelf = self { + let pagerIndex = indexes[index] + strongSelf.pager.transaction(GalleryPagerTransaction(deleteItems: [], insertItems: [], updateItems: [], focusOnItem: pagerIndex)) } } } diff --git a/TelegramUI/GalleryThumbnailContainerNode.swift b/TelegramUI/GalleryThumbnailContainerNode.swift index 42b05eeb87..b96e3fd6c5 100644 --- a/TelegramUI/GalleryThumbnailContainerNode.swift +++ b/TelegramUI/GalleryThumbnailContainerNode.swift @@ -52,6 +52,7 @@ final class GalleryThumbnailContainerNode: ASDisplayNode, UIScrollViewDelegate { private let scrollNode: ASScrollNode private(set) var items: [GalleryThumbnailItem] = [] + private(set) var indexes: [Int] = [] private var itemNodes: [GalleryThumbnailItemNode] = [] private var centralIndexAndProgress: (Int, CGFloat?)? private var currentLayout: CGSize? @@ -84,7 +85,8 @@ final class GalleryThumbnailContainerNode: ASDisplayNode, UIScrollViewDelegate { } } - func updateItems(_ items: [GalleryThumbnailItem], centralIndex: Int, progress: CGFloat) { + func updateItems(_ items: [GalleryThumbnailItem], indexes: [Int], centralIndex: Int, progress: CGFloat) { + self.indexes = indexes var items: [GalleryThumbnailItem] = items.count <= 1 ? [] : items var updated = false if self.items.count == items.count { diff --git a/TelegramUI/GeneratedMediaStoreSettings.swift b/TelegramUI/GeneratedMediaStoreSettings.swift index a5bb8f0954..b693ddddf0 100644 --- a/TelegramUI/GeneratedMediaStoreSettings.swift +++ b/TelegramUI/GeneratedMediaStoreSettings.swift @@ -4,21 +4,25 @@ import SwiftSignalKit public struct GeneratedMediaStoreSettings: PreferencesEntry, Equatable { public let storeEditedPhotos: Bool + public let storeCapturedMedia: Bool public static var defaultSettings: GeneratedMediaStoreSettings { - return GeneratedMediaStoreSettings(storeEditedPhotos: true) + return GeneratedMediaStoreSettings(storeEditedPhotos: true, storeCapturedMedia: true) } - init(storeEditedPhotos: Bool) { + init(storeEditedPhotos: Bool, storeCapturedMedia: Bool) { self.storeEditedPhotos = storeEditedPhotos + self.storeCapturedMedia = storeCapturedMedia } public init(decoder: PostboxDecoder) { self.storeEditedPhotos = decoder.decodeInt32ForKey("eph", orElse: 0) != 0 + self.storeCapturedMedia = decoder.decodeInt32ForKey("cpm", orElse: 0) != 0 } public func encode(_ encoder: PostboxEncoder) { encoder.encodeInt32(self.storeEditedPhotos ? 1 : 0, forKey: "eph") + encoder.encodeInt32(self.storeCapturedMedia ? 1 : 0, forKey: "cpm") } public func isEqual(to: PreferencesEntry) -> Bool { @@ -30,11 +34,11 @@ public struct GeneratedMediaStoreSettings: PreferencesEntry, Equatable { } public static func ==(lhs: GeneratedMediaStoreSettings, rhs: GeneratedMediaStoreSettings) -> Bool { - return lhs.storeEditedPhotos == rhs.storeEditedPhotos + return lhs.storeEditedPhotos == rhs.storeEditedPhotos && lhs.storeCapturedMedia == rhs.storeCapturedMedia } func withUpdatedStoreEditedPhotos(_ storeEditedPhotos: Bool) -> GeneratedMediaStoreSettings { - return GeneratedMediaStoreSettings(storeEditedPhotos: storeEditedPhotos) + return GeneratedMediaStoreSettings(storeEditedPhotos: storeEditedPhotos, storeCapturedMedia: self.storeCapturedMedia) } } diff --git a/TelegramUI/GeoLocation.swift b/TelegramUI/GeoLocation.swift deleted file mode 100644 index 5c159ce0e0..0000000000 --- a/TelegramUI/GeoLocation.swift +++ /dev/null @@ -1,85 +0,0 @@ -import Foundation -import CoreLocation -import SwiftSignalKit - -enum GeoLocation { - case location(CLLocation) - case unavailable -} - -private final class LocationHelper: NSObject, CLLocationManagerDelegate { - private let queue: Queue - private var locationManager: CLLocationManager? - let location = Promise() - private var startedUpdating = false - - init(queue: Queue) { - self.queue = queue - - super.init() - - queue.async { - let locationManager = CLLocationManager() - self.locationManager = locationManager - locationManager.delegate = self - switch CLLocationManager.authorizationStatus() { - case .authorizedAlways, .authorizedWhenInUse: - locationManager.startUpdatingLocation() - case .denied, .restricted: - self.location.set(.single(.unavailable)) - case .notDetermined: - locationManager.requestWhenInUseAuthorization() - locationManager.startUpdatingLocation() - } - } - } - - deinit { - if let locationManager = self.locationManager { - self.queue.async { - locationManager.stopUpdatingLocation() - } - } - } - - func stop() { - if let locationManager = self.locationManager { - self.queue.async { - locationManager.stopUpdatingLocation() - } - } - } - - func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { - self.queue.async { - if !locations.isEmpty { - self.location.set(.single(.location(locations[locations.count - 1]))) - } - } - } - - func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) { - self.queue.async { - switch status { - case .denied, .restricted: - self.location.set(.single(.unavailable)) - default: - break - } - } - } -} - -func currentGeoLocation() -> Signal { - return Signal { subscriber in - let queue = Queue() - let helper = LocationHelper(queue: queue) - let disposable = (helper.location.get() |> deliverOn(queue)).start(next: { location in - subscriber.putNext(location) - }) - return ActionDisposable { - helper.stop() - } - } - return .complete() -} diff --git a/TelegramUI/HashtagSearchController.swift b/TelegramUI/HashtagSearchController.swift index d894790f9b..6d2967e416 100644 --- a/TelegramUI/HashtagSearchController.swift +++ b/TelegramUI/HashtagSearchController.swift @@ -56,6 +56,7 @@ final class HashtagSearchController: TelegramController { strongSelf.controllerNode.listNode.clearHighlightAnimated(true) } }, groupSelected: { _ in + }, addContact: {_ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in diff --git a/TelegramUI/ImageTransparency.swift b/TelegramUI/ImageTransparency.swift new file mode 100644 index 0000000000..517797a080 --- /dev/null +++ b/TelegramUI/ImageTransparency.swift @@ -0,0 +1,55 @@ +import UIKit +import Accelerate + +func imageHasTransparency(_ image: UIImage) -> Bool { + guard let cgImage = image.cgImage, cgImage.bitsPerComponent == 8, cgImage.bitsPerPixel == 32 else { + return false + } + let alphaInfo = cgImage.alphaInfo + guard alphaInfo == .first || alphaInfo == .last || alphaInfo == .premultipliedFirst || alphaInfo == .premultipliedLast else { + return false + } + + var sourceBuffer = vImage_Buffer() + defer { + free(sourceBuffer.data) + } + + var cgImageFormat = vImage_CGImageFormat( + bitsPerComponent: UInt32(cgImage.bitsPerComponent), + bitsPerPixel: UInt32(cgImage.bitsPerPixel), + colorSpace: Unmanaged.passUnretained(cgImage.colorSpace!), + bitmapInfo: cgImage.bitmapInfo, + version: 0, + decode: nil, + renderingIntent: .defaultIntent + ) + + let noFlags = vImage_Flags(kvImageNoFlags) + var error = vImageBuffer_InitWithCGImage(&sourceBuffer, &cgImageFormat, nil, cgImage, noFlags) + assert(error == kvImageNoError) + + if alphaInfo == .premultipliedLast { + error = vImageUnpremultiplyData_RGBA8888(&sourceBuffer, &sourceBuffer, noFlags) + } else if alphaInfo == .premultipliedFirst { + error = vImageUnpremultiplyData_ARGB8888(&sourceBuffer, &sourceBuffer, noFlags) + } + assert(error == kvImageNoError) + + let histogramBins = (0...3).map { _ in + return [vImagePixelCount](repeating: 0, count: 256) + } + var mutableHistogram: [UnsafeMutablePointer?] = histogramBins.map { + return UnsafeMutablePointer(mutating: $0) + } + error = vImageHistogramCalculation_ARGB8888(&sourceBuffer, &mutableHistogram, noFlags) + assert(error == kvImageNoError) + + let alphaBinIndex = alphaInfo == .last || alphaInfo == .premultipliedLast ? 3 : 0 + for i in 0 ..< 255 { + if histogramBins[alphaBinIndex][i] > 0 { + return true + } + } + return false +} diff --git a/TelegramUI/InstantImageGalleryItem.swift b/TelegramUI/InstantImageGalleryItem.swift index 11759238ba..baa2c9d241 100644 --- a/TelegramUI/InstantImageGalleryItem.swift +++ b/TelegramUI/InstantImageGalleryItem.swift @@ -33,15 +33,17 @@ class InstantImageGalleryItem: GalleryItem { let presentationData: PresentationData let imageReference: ImageMediaReference let caption: NSAttributedString + let credit: NSAttributedString let location: InstantPageGalleryEntryLocation let openUrl: (InstantPageUrlItem) -> Void let openUrlOptions: (InstantPageUrlItem) -> Void - init(account: Account, presentationData: PresentationData, imageReference: ImageMediaReference, caption: NSAttributedString, location: InstantPageGalleryEntryLocation, openUrl: @escaping (InstantPageUrlItem) -> Void, openUrlOptions: @escaping (InstantPageUrlItem) -> Void) { + init(account: Account, presentationData: PresentationData, imageReference: ImageMediaReference, caption: NSAttributedString, credit: NSAttributedString, location: InstantPageGalleryEntryLocation, openUrl: @escaping (InstantPageUrlItem) -> Void, openUrlOptions: @escaping (InstantPageUrlItem) -> Void) { self.account = account self.presentationData = presentationData self.imageReference = imageReference self.caption = caption + self.credit = credit self.location = location self.openUrl = openUrl self.openUrlOptions = openUrlOptions @@ -54,7 +56,7 @@ class InstantImageGalleryItem: GalleryItem { node._title.set(.single("\(self.location.position + 1) \(self.presentationData.strings.Common_of) \(self.location.totalCount)")) - node.setCaption(self.caption) + node.setCaption(self.caption, credit: self.credit) return node } @@ -63,7 +65,7 @@ class InstantImageGalleryItem: GalleryItem { if let node = node as? InstantImageGalleryItemNode { node._title.set(.single("\(self.location.position + 1) \(self.presentationData.strings.Common_of) \(self.location.totalCount)")) - node.setCaption(self.caption) + node.setCaption(self.caption, credit: self.credit) } } @@ -114,8 +116,8 @@ final class InstantImageGalleryItemNode: ZoomableContentGalleryItemNode { super.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition) } - fileprivate func setCaption(_ caption: NSAttributedString) { - self.footerContentNode.setCaption(caption) + fileprivate func setCaption(_ caption: NSAttributedString, credit: NSAttributedString) { + self.footerContentNode.setCaption(caption, credit: credit) } fileprivate func setImage(imageReference: ImageMediaReference) { diff --git a/TelegramUI/InstantPageControllerNode.swift b/TelegramUI/InstantPageControllerNode.swift index 10f5befe44..0524dcfb4c 100644 --- a/TelegramUI/InstantPageControllerNode.swift +++ b/TelegramUI/InstantPageControllerNode.swift @@ -921,7 +921,36 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { } } - self.resolveUrlDisposable.set((resolveUrl(account: self.account, url: url.url) |> deliverOnMainQueue).start(next: { [weak self] result in + var cancelImpl: (() -> Void)? + let progressSignal = Signal { [weak self] subscriber in + guard let strongSelf = self else { + return EmptyDisposable + } + + let controller = OverlayStatusController(theme: strongSelf.presentationTheme, type: .loading(cancelled: { + cancelImpl?() + })) + strongSelf.present(controller, nil) + return ActionDisposable { [weak controller] in + Queue.mainQueue().async() { + controller?.dismiss() + } + } + } + |> runOn(Queue.mainQueue()) + |> delay(0.15, queue: Queue.mainQueue()) + let progressDisposable = progressSignal.start() + + let resolveSignal = resolveUrl(account: self.account, url: url.url) + |> afterDisposed { + Queue.mainQueue().async { + progressDisposable.dispose() + } + } + cancelImpl = { [weak self] in + self?.resolveUrlDisposable.set(nil) + } + self.resolveUrlDisposable.set((resolveSignal |> deliverOnMainQueue).start(next: { [weak self] result in if let strongSelf = self { switch result { case let .externalUrl(externalUrl): @@ -1020,7 +1049,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, credit: media.credit, location: InstantPageGalleryEntryLocation(position: Int32(entries.count), totalCount: Int32(medias.count)))) } var centralIndex: Int? diff --git a/TelegramUI/InstantPageDetailsItem.swift b/TelegramUI/InstantPageDetailsItem.swift index 993651c8ba..757f101c46 100644 --- a/TelegramUI/InstantPageDetailsItem.swift +++ b/TelegramUI/InstantPageDetailsItem.swift @@ -69,10 +69,13 @@ final class InstantPageDetailsItem: InstantPageItem { } } } else { + let convertedPoint = point.offsetBy(dx: 0.0, dy: -self.titleHeight) for item in self.items { - if item.frame.contains(point) { - let rects = item.linkSelectionRects(at: point.offsetBy(dx: 0.0, dy: -self.titleHeight)) - return rects.map { $0.offsetBy(dx: item.frame.minX, dy: item.frame.minY + self.titleHeight) } + if item.frame.contains(convertedPoint) { + let rects = item.linkSelectionRects(at: convertedPoint.offsetBy(dx: -item.frame.minX, dy: -item.frame.minY)) + if !rects.isEmpty { + return rects.map { $0.offsetBy(dx: item.frame.minX, dy: item.frame.minY + self.titleHeight) } + } } } } diff --git a/TelegramUI/InstantPageGalleryController.swift b/TelegramUI/InstantPageGalleryController.swift index bc8a6db005..e92014cd04 100644 --- a/TelegramUI/InstantPageGalleryController.swift +++ b/TelegramUI/InstantPageGalleryController.swift @@ -21,31 +21,44 @@ struct InstantPageGalleryEntry: Equatable { let pageId: MediaId let media: InstantPageMedia let caption: RichText? + let credit: 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 + return lhs.index == rhs.index && lhs.pageId == rhs.pageId && lhs.media == rhs.media && lhs.caption == rhs.caption && lhs.credit == rhs.credit && lhs.location == rhs.location } 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) + let caption: NSAttributedString + let credit: NSAttributedString + if let mediaCaption = self.media.caption { + 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)) + caption = attributedStringForRichText(mediaCaption, styleStack: styleStack) } else { - attributedString = NSAttributedString(string: "") + caption = NSAttributedString(string: "") + } + + if let mediaCredit = self.media.credit { + let styleStack = InstantPageTextStyleStack() + styleStack.push(.fontSize(14.0)) + styleStack.push(.textColor(.white)) + styleStack.push(.markerColor(UIColor(rgb: 0x313131))) + styleStack.push(.fontSerif(false)) + styleStack.push(.lineSpacingFactor(1.0)) + credit = attributedStringForRichText(mediaCredit, styleStack: styleStack) + } else { + credit = NSAttributedString(string: "") } if let image = self.media.media as? TelegramMediaImage { - return InstantImageGalleryItem(account: account, presentationData: presentationData, imageReference: .webPage(webPage: WebpageReference(webPage), media: image), caption: attributedString, location: self.location, openUrl: openUrl, openUrlOptions: openUrlOptions) + return InstantImageGalleryItem(account: account, presentationData: presentationData, imageReference: .webPage(webPage: WebpageReference(webPage), media: image), caption: caption, credit: credit, 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: attributedString, openUrl: { _ in }, openUrlOptions: { _ in }) + 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: caption, credit: credit, openUrl: { _ in }, openUrlOptions: { _ in }) } else { preconditionFailure() } diff --git a/TelegramUI/InstantPageGalleryFooterContentNode.swift b/TelegramUI/InstantPageGalleryFooterContentNode.swift index e12b372ade..705f0bdf0c 100644 --- a/TelegramUI/InstantPageGalleryFooterContentNode.swift +++ b/TelegramUI/InstantPageGalleryFooterContentNode.swift @@ -64,16 +64,30 @@ final class InstantPageGalleryFooterContentNode: GalleryFooterContentNode { self.actionButton.addTarget(self, action: #selector(self.actionButtonPressed), for: [.touchUpInside]) } - func setCaption(_ caption: NSAttributedString) { + func setCaption(_ caption: NSAttributedString, credit: NSAttributedString) { if self.currentMessageText != caption { self.currentMessageText = caption - if caption.length == 0 { + var attributedText: NSMutableAttributedString? + if caption.length > 0 { + attributedText = NSMutableAttributedString(attributedString: caption) + } + + if credit.length > 0 { + if attributedText != nil { + attributedText?.append(NSAttributedString(string: "\n")) + attributedText?.append(credit) + } else { + attributedText = NSMutableAttributedString(attributedString: credit) + } + } + + if let attributedText = attributedText { + self.textNode.isHidden = false + self.textNode.attributedText = attributedText + } else { self.textNode.isHidden = true self.textNode.attributedText = nil - } else { - self.textNode.isHidden = false - self.textNode.attributedText = caption } self.requestLayout?(.immediate) diff --git a/TelegramUI/InstantPageImageItem.swift b/TelegramUI/InstantPageImageItem.swift index 2f24f3e06a..a69e28c1e1 100644 --- a/TelegramUI/InstantPageImageItem.swift +++ b/TelegramUI/InstantPageImageItem.swift @@ -15,7 +15,6 @@ final class InstantPageImageItem: InstantPageItem { var frame: CGRect let webPage: TelegramMediaWebpage - let url: InstantPageUrlItem? let media: InstantPageMedia let attributes: [InstantPageImageAttribute] @@ -31,19 +30,18 @@ final class InstantPageImageItem: InstantPageItem { let wantsNode: Bool = true let separatesTiles: Bool = false - init(frame: CGRect, webPage: TelegramMediaWebpage, media: InstantPageMedia, attributes: [InstantPageImageAttribute] = [], url: InstantPageUrlItem? = nil, interactive: Bool, roundCorners: Bool, fit: Bool) { + init(frame: CGRect, webPage: TelegramMediaWebpage, media: InstantPageMedia, attributes: [InstantPageImageAttribute] = [], 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, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? { - return InstantPageImageNode(account: account, theme: theme, 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) + return InstantPageImageNode(account: account, theme: theme, webPage: self.webPage, media: self.media, attributes: self.attributes, interactive: self.interactive, roundCorners: self.roundCorners, fit: self.fit, openMedia: openMedia) } func matchesAnchor(_ anchor: String) -> Bool { diff --git a/TelegramUI/InstantPageImageNode.swift b/TelegramUI/InstantPageImageNode.swift index 8741c964a2..ae497a01b0 100644 --- a/TelegramUI/InstantPageImageNode.swift +++ b/TelegramUI/InstantPageImageNode.swift @@ -11,12 +11,10 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { private var theme: InstantPageTheme 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 @@ -27,18 +25,16 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { private var themeUpdated: Bool = false - init(account: Account, theme: InstantPageTheme, webPage: TelegramMediaWebpage, media: InstantPageMedia, attributes: [InstantPageImageAttribute], url: InstantPageUrlItem? = nil, interactive: Bool, roundCorners: Bool, fit: Bool, openMedia: @escaping (InstantPageMedia) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void = { _ in }) { + init(account: Account, theme: InstantPageTheme, webPage: TelegramMediaWebpage, media: InstantPageMedia, attributes: [InstantPageImageAttribute], interactive: Bool, roundCorners: Bool, fit: Bool, openMedia: @escaping (InstantPageMedia) -> Void) { self.account = account self.theme = theme self.webPage = webPage 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() @@ -99,14 +95,6 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { self.theme = theme self.themeUpdated = true self.setNeedsLayout() - - if let file = self.media.media as? TelegramMediaFile { - let fileReference = FileMediaReference.webPage(webPage: WebpageReference(webPage), media: file) - if file.mimeType.hasPrefix("image/") { - _ = freeMediaFileInteractiveFetched(account: self.account, fileReference: fileReference).start() - self.imageNode.setSignal(chatMessageImageFile(account: self.account, fileReference: fileReference, thumbnail: false, fetched: true)) - } - } } } @@ -126,7 +114,7 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { 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())) + let apply = makeLayout(TransformImageArguments(corners: ImageCorners(radius: radius), imageSize: imageSize, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets(), emptyColor: self.theme.pageBackgroundColor)) apply() } else if let file = self.media.media as? TelegramMediaFile, let dimensions = file.dimensions { let emptyColor = file.mimeType.hasPrefix("image/") ? self.theme.imageEmptyColor : nil @@ -175,11 +163,7 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { @objc func tapGesture(_ recognizer: UITapGestureRecognizer) { if case .ended = recognizer.state { - if let url = self.url { - self.openUrl(url) - } else { - self.openMedia(self.media) - } + self.openMedia(self.media) } } } diff --git a/TelegramUI/InstantPageLayout.swift b/TelegramUI/InstantPageLayout.swift index bd7712ce89..4b77c413ed 100644 --- a/TelegramUI/InstantPageLayout.swift +++ b/TelegramUI/InstantPageLayout.swift @@ -41,7 +41,7 @@ private func setupStyleStack(_ stack: InstantPageTextStyleStack, theme: InstantP } } -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, detailsIndexCounter: inout Int, theme: InstantPageTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, webEmbedHeights: [Int : CGFloat] = [:]) -> InstantPageLayout { +func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: InstantPageBlock, boundingWidth: CGFloat, horizontalInset: CGFloat, safeInset: CGFloat, isCover: Bool, previousItems: [InstantPageItem], fillToSize: CGSize?, media: [MediaId: Media], mediaIndexCounter: inout Int, embedIndexCounter: inout Int, detailsIndexCounter: inout Int, theme: InstantPageTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, webEmbedHeights: [Int : CGFloat] = [:]) -> InstantPageLayout { let layoutCaption: (InstantPageCaption, CGSize) -> ([InstantPageItem], CGSize) = { caption, contentSize in var items: [InstantPageItem] = [] @@ -69,7 +69,7 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins offset += 10.0 } let styleStack = InstantPageTextStyleStack() - setupStyleStack(styleStack, theme: theme, category: .caption, link: false) + setupStyleStack(styleStack, theme: theme, category: .credit, 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 @@ -80,7 +80,7 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins switch block { case let .cover(block): - 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, detailsIndexCounter: &detailsIndexCounter, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, webEmbedHeights: webEmbedHeights) + return layoutInstantPageBlock(webpage: webpage, rtl: rtl, block: block, boundingWidth: boundingWidth, horizontalInset: horizontalInset, safeInset: safeInset, isCover: true, previousItems:previousItems, fillToSize: fillToSize, media: media, mediaIndexCounter: &mediaIndexCounter, embedIndexCounter: &embedIndexCounter, detailsIndexCounter: &detailsIndexCounter, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, webEmbedHeights: webEmbedHeights) case let .title(text): let styleStack = InstantPageTextStyleStack() setupStyleStack(styleStack, theme: theme, category: .header, link: false) @@ -173,7 +173,7 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins let styleStack = InstantPageTextStyleStack() setupStyleStack(styleStack, theme: theme, category: .paragraph, link: false) let backgroundInset: CGFloat = 14.0 - 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 (items, contentSize) = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0 - backgroundInset * 2.0, offset: CGPoint(x: 17.0, 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) @@ -265,7 +265,7 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins 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, detailsIndexCounter: &detailsIndexCounter, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, webEmbedHeights: webEmbedHeights) + 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, fillToSize: nil, media: media, mediaIndexCounter: &mediaIndexCounter, embedIndexCounter: &embedIndexCounter, detailsIndexCounter: &detailsIndexCounter, 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)) @@ -369,8 +369,8 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins let imageSize = largest.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) + if let size = fillToSize { + filledSize = size } else if isCover { filledSize = imageSize.aspectFilled(CGSize(width: boundingWidth - safeInset * 2.0, height: 1.0)) if !filledSize.height.isZero { @@ -384,12 +384,12 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins var contentSize = CGSize(width: boundingWidth - safeInset * 2.0, height: 0.0) var items: [InstantPageItem] = [] - var urlItem: InstantPageUrlItem? + var mediaUrl: InstantPageUrlItem? if let url = url { - urlItem = InstantPageUrlItem(url: url, webpageId: webpageId) + mediaUrl = 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) + 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, url: mediaUrl, caption: caption.text, credit: caption.credit), interactive: true, roundCorners: false, fit: false) items.append(mediaItem) contentSize.height += filledSize.height @@ -407,8 +407,8 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins 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) + if let size = fillToSize { + filledSize = size } else if isCover { filledSize = imageSize.aspectFilled(CGSize(width: boundingWidth - safeInset * 2.0, height: 1.0)) if !filledSize.height.isZero { @@ -423,11 +423,11 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins 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.text), 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, url: nil, caption: caption.text, credit: caption.credit), 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.text), url: nil, 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, url: nil, caption: caption.text, credit: caption.credit), interactive: true, roundCorners: false, fit: false) items.append(mediaItem) } @@ -442,24 +442,35 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins return InstantPageLayout(origin: CGPoint(), contentSize: CGSize(), items: []) } case let .collage(items: innerItems, caption: caption): - let spacing: CGFloat = 2.0 - let itemsPerRow = 3 - let itemSize = floor((boundingWidth - safeInset * 2.0 - spacing * max(0.0, CGFloat(itemsPerRow - 1))) / CGFloat(itemsPerRow)) - var items: [InstantPageItem] = [] - - var nextItemOrigin = CGPoint(x: 0.0, y: 0.0) + var itemSizes: [CGSize] = [] for subItem in innerItems { - if nextItemOrigin.x + itemSize > boundingWidth { - nextItemOrigin.x = 0.0 - nextItemOrigin.y += itemSize + spacing + var size = CGSize() + switch subItem { + case let .image(id, _, _, _): + if let image = media[id] as? TelegramMediaImage, let largest = largestImageRepresentation(image.representations) { + size = largest.dimensions + } + case let .video(id, _, _, _): + if let file = media[id] as? TelegramMediaFile, let dimensions = file.dimensions { + size = dimensions + } + default: + break } - 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, detailsIndexCounter: &detailsIndexCounter, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, webEmbedHeights: webEmbedHeights) - items.append(contentsOf: subLayout.flattenedItemsWithOrigin(nextItemOrigin)) - nextItemOrigin.x += itemSize + spacing + itemSizes.append(size) + } + let (mosaicLayout, mosaicSize) = chatMessageBubbleMosaicLayout(maxSize: CGSize(width: boundingWidth, height: boundingWidth), itemSizes: itemSizes) + + var i = 0 + for subItem in innerItems { + let frame = mosaicLayout[i].0 + let subLayout = layoutInstantPageBlock(webpage: webpage, rtl: rtl, block: subItem, boundingWidth: frame.width, horizontalInset: 0.0, safeInset: 0.0, isCover: false, previousItems: items, fillToSize: frame.size, media: media, mediaIndexCounter: &mediaIndexCounter, embedIndexCounter: &embedIndexCounter, detailsIndexCounter: &detailsIndexCounter, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, webEmbedHeights: webEmbedHeights) + items.append(contentsOf: subLayout.flattenedItemsWithOrigin(frame.origin)) + i += 1 } - var contentSize = CGSize(width: boundingWidth - safeInset * 2.0, height: nextItemOrigin.y + itemSize) + var contentSize = CGSize(width: boundingWidth - safeInset * 2.0, height: mosaicSize.height) let (captionItems, captionSize) = layoutCaption(caption, contentSize) items.append(contentsOf: captionItems) @@ -481,7 +492,7 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins 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: nil), url: nil, 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, url: nil, caption: nil, credit: nil), interactive: false, roundCorners: true, fit: false) items.append(avatarItem) avatarInset += 62.0 @@ -522,7 +533,7 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins var previousBlock: InstantPageBlock? for subBlock in blocks { - 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, detailsIndexCounter: &detailsIndexCounter, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, webEmbedHeights: webEmbedHeights) + 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, fillToSize: nil, media: media, mediaIndexCounter: &mediaIndexCounter, embedIndexCounter: &embedIndexCounter, detailsIndexCounter: &detailsIndexCounter, 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)) @@ -556,7 +567,11 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins 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.text)) + var mediaUrl: InstantPageUrlItem? + if let url = url { + mediaUrl = InstantPageUrlItem(url: url, webpageId: webpageId) + } + itemMedias.append(InstantPageMedia(index: mediaIndex, media: image, url: mediaUrl, caption: caption.text, credit: caption.credit)) } break default: @@ -631,7 +646,7 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins 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: nil), 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, url: nil, caption: nil, credit: nil), webpage: webpage) contentSize.height += item.frame.height items.append(item) @@ -678,7 +693,7 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins var previousBlock: InstantPageBlock? for subBlock in blocks { - let subLayout = layoutInstantPageBlock(webpage: webpage, rtl: rtl, block: subBlock, boundingWidth: boundingWidth, horizontalInset: horizontalInset, safeInset: safeInset, isCover: false, previousItems: subitems, fillToWidthAndHeight: false, media: media, mediaIndexCounter: &mediaIndexCounter, embedIndexCounter: &embedIndexCounter, detailsIndexCounter: &subDetailsIndex, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, webEmbedHeights: webEmbedHeights) + let subLayout = layoutInstantPageBlock(webpage: webpage, rtl: rtl, block: subBlock, boundingWidth: boundingWidth, horizontalInset: horizontalInset, safeInset: safeInset, isCover: false, previousItems: subitems, fillToSize: nil, media: media, mediaIndexCounter: &mediaIndexCounter, embedIndexCounter: &embedIndexCounter, detailsIndexCounter: &subDetailsIndex, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, webEmbedHeights: webEmbedHeights) let spacing = spacingBetweenBlocks(upper: previousBlock, lower: subBlock) let blockItems = subLayout.flattenedItemsWithOrigin(CGPoint(x: 0.0, y: contentSize.height + spacing)) @@ -741,8 +756,8 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins 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) + if let size = fillToSize { + filledSize = size } else if isCover { filledSize = imageSize.aspectFilled(CGSize(width: boundingWidth - safeInset * 2.0, height: 1.0)) if !filledSize.height.isZero { @@ -755,7 +770,7 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins 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) + 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, url: nil, caption: caption.text, credit: caption.credit), attributes: attributes, interactive: true, roundCorners: false, fit: false) items.append(mediaItem) contentSize.height += filledSize.height @@ -796,7 +811,7 @@ func instantPageLayoutForWebPage(_ webPage: TelegramMediaWebpage, boundingWidth: var previousBlock: InstantPageBlock? for block in pageBlocks { - 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, detailsIndexCounter: &detailsIndexCounter, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, webEmbedHeights: webEmbedHeights) + let blockLayout = layoutInstantPageBlock(webpage: webPage, rtl: rtl, block: block, boundingWidth: boundingWidth, horizontalInset: 17.0 + safeInset, safeInset: safeInset, isCover: false, previousItems: items, fillToSize: nil, media: media, mediaIndexCounter: &mediaIndexCounter, embedIndexCounter: &embedIndexCounter, detailsIndexCounter: &detailsIndexCounter, 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) diff --git a/TelegramUI/InstantPageMedia.swift b/TelegramUI/InstantPageMedia.swift index 1ab41a6caf..eb0e824492 100644 --- a/TelegramUI/InstantPageMedia.swift +++ b/TelegramUI/InstantPageMedia.swift @@ -5,9 +5,11 @@ import TelegramCore struct InstantPageMedia: Equatable { let index: Int let media: Media + let url: InstantPageUrlItem? let caption: RichText? + let credit: RichText? static func ==(lhs: InstantPageMedia, rhs: InstantPageMedia) -> Bool { - return lhs.index == rhs.index && lhs.media.isEqual(to: rhs.media) && lhs.caption == rhs.caption + return lhs.index == rhs.index && lhs.media.isEqual(to: rhs.media) && lhs.url == rhs.url && lhs.caption == rhs.caption && lhs.credit == rhs.credit } } diff --git a/TelegramUI/InstantPageSlideshowItemNode.swift b/TelegramUI/InstantPageSlideshowItemNode.swift index ec2c764027..6ab57c9522 100644 --- a/TelegramUI/InstantPageSlideshowItemNode.swift +++ b/TelegramUI/InstantPageSlideshowItemNode.swift @@ -384,7 +384,8 @@ final class InstantPageSlideshowNode: ASDisplayNode, InstantPageNode { super.init() - self.backgroundColor = .black + self.backgroundColor = theme.panelSecondaryColor + self.clipsToBounds = true self.addSubnode(self.pagerNode) self.addSubnode(self.pageControlNode) @@ -430,5 +431,6 @@ final class InstantPageSlideshowNode: ASDisplayNode, InstantPageNode { } func update(strings: PresentationStrings, theme: InstantPageTheme) { + self.backgroundColor = theme.panelSecondaryColor } } diff --git a/TelegramUI/InstantPageTableItem.swift b/TelegramUI/InstantPageTableItem.swift index bf2ff49646..e74cd538d4 100644 --- a/TelegramUI/InstantPageTableItem.swift +++ b/TelegramUI/InstantPageTableItem.swift @@ -66,11 +66,11 @@ private struct 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) { + if adjacentSides.contains(.left) && !adjacentSides.contains(.right) { adjacentSides.remove(.left) adjacentSides.insert(.right) } - if adjacentSides.contains(.right) { + else if adjacentSides.contains(.right) && !adjacentSides.contains(.left) { adjacentSides.remove(.right) adjacentSides.insert(.left) } @@ -120,46 +120,53 @@ final class InstantPageTableItem: InstantPageItem { func drawInTile(context: CGContext) { for cell in self.cells { + if cell.textItem == nil && cell.additionalItems.isEmpty { + continue + } context.saveGState() context.translateBy(x: cell.frame.minX, y: cell.frame.minY) + + let hasBorder = self.borderWidth > 0.0 + let bounds = CGRect(origin: CGPoint(), size: cell.frame.size) + var path: UIBezierPath? + if !cell.adjacentSides.isEmpty { + path = UIBezierPath(roundedRect: bounds, byRoundingCorners: cell.adjacentSides.uiRectCorner, cornerRadii: CGSize(width: tableCornerRadius, height: tableCornerRadius)) + } 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 { + } + if self.borderWidth > 0.0 { + context.setStrokeColor(self.theme.tableBorderColor.cgColor) + context.setLineWidth(borderWidth) + } + if let path = path { + context.addPath(path.cgPath) + var drawMode: CGPathDrawingMode? + switch (cell.filled, hasBorder) { + case (true, false): + drawMode = .fill + case (true, true): + drawMode = .fillStroke + case (false, true): + drawMode = .stroke + default: + break + } + if let drawMode = drawMode { + context.drawPath(using: drawMode) + } + } else { + if cell.filled { context.fill(bounds) } + if hasBorder { + context.stroke(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() } } @@ -383,7 +390,7 @@ func layoutTableItem(rtl: Bool, rows: [InstantPageTableRow], styleStack: Instant columnCount = max(columnCount, row.cells.count) } - let maxContentWidth = boundingWidth - borderWidth * CGFloat(columnCount - 1) + let maxContentWidth = boundingWidth - borderWidth // - borderWidth * CGFloat(columnCount - 1) var availableWidth = maxContentWidth var minColumnWidths: [Int : CGFloat] = [:] var maxColumnWidths: [Int : CGFloat] = [:] @@ -454,19 +461,22 @@ func layoutTableItem(rtl: Bool, rows: [InstantPageTableRow], styleStack: Instant } if widthToDistribute > 0.0 { + var distributedWidth = widthToDistribute for i in 0 ..< finalColumnWidths.count { var width = finalColumnWidths[i]! let maxWidth = maxColumnWidths[i]! - let growth = round(widthToDistribute * maxWidth / maxTotalWidth) + let growth = min(round(widthToDistribute * maxWidth / maxTotalWidth), distributedWidth) width += growth - availableWidth -= growth + distributedWidth -= growth finalColumnWidths[i] = width } totalWidth = boundingWidth + } else { + totalWidth += borderWidth } var finalizedCells: [InstantPageTableCellItem] = [] - var origin: CGPoint = CGPoint() + var origin: CGPoint = CGPoint(x: borderWidth / 2.0, y: borderWidth / 2.0) var totalHeight: CGFloat = 0.0 var rowHeights: [Int : CGFloat] = [:] @@ -476,7 +486,7 @@ func layoutTableItem(rtl: Bool, rows: [InstantPageTableRow], styleStack: Instant let row = rows[i] var maxRowHeight: CGFloat = 1.0 var isEmptyRow = true - origin.x = 0.0 + origin.x = borderWidth / 2.0 var k: Int = 0 var rowCells: [InstantPageTableCellItem] = [] @@ -491,7 +501,7 @@ func layoutTableItem(rtl: Bool, rows: [InstantPageTableRow], styleStack: Instant origin.x += width } } - origin.x += borderWidth * CGFloat(cell.colspan) + //origin.x += borderWidth * CGFloat(cell.colspan) k += cell.colspan } else { break @@ -508,7 +518,7 @@ func layoutTableItem(rtl: Bool, rows: [InstantPageTableRow], styleStack: Instant cellWidth += width } } - cellWidth += borderWidth * CGFloat(colspan - 1) + //cellWidth += borderWidth * CGFloat(colspan - 1) var item: InstantPageTextItem? var additionalItems: [InstantPageItem] = [] @@ -572,7 +582,7 @@ func layoutTableItem(rtl: Bool, rows: [InstantPageTableRow], styleStack: Instant } k += colspan - origin.x += cellWidth + borderWidth + origin.x += cellWidth //+ borderWidth } let finalizeCell: (InstantPageTableCellItem, inout [InstantPageTableCellItem], CGFloat) -> Void = { cell, cells, height in @@ -623,7 +633,7 @@ func layoutTableItem(rtl: Bool, rows: [InstantPageTableRow], styleStack: Instant completedSpans[k]!.insert(colAndCell.0) } } - cellHeight += borderWidth * CGFloat(cell.rowspan - 1) + //cellHeight += borderWidth * CGFloat(cell.rowspan - 1) if cell.frame.height > cellHeight { let delta = cell.frame.height - cellHeight @@ -659,10 +669,11 @@ func layoutTableItem(rtl: Bool, rows: [InstantPageTableRow], styleStack: Instant if !isEmptyRow { totalHeight += maxRowHeight - origin.y += maxRowHeight + borderWidth + origin.y += maxRowHeight //+ borderWidth } } - totalHeight += borderWidth * CGFloat(rowHeights.count - 1) + totalHeight += borderWidth + //totalHeight += borderWidth * CGFloat(rowHeights.count - 1) if rtl { finalizedCells = finalizedCells.map { $0.withRTL(totalWidth) } diff --git a/TelegramUI/InstantPageTextItem.swift b/TelegramUI/InstantPageTextItem.swift index 54d02c420c..644bc4868f 100644 --- a/TelegramUI/InstantPageTextItem.swift +++ b/TelegramUI/InstantPageTextItem.swift @@ -4,7 +4,7 @@ import Display import Postbox import AsyncDisplayKit -final class InstantPageUrlItem { +final class InstantPageUrlItem: Equatable { let url: String let webpageId: MediaId? @@ -12,6 +12,10 @@ final class InstantPageUrlItem { self.url = url self.webpageId = webpageId } + + public static func ==(lhs: InstantPageUrlItem, rhs: InstantPageUrlItem) -> Bool { + return lhs.url == rhs.url && lhs.webpageId == rhs.webpageId + } } struct InstantPageTextMarkedItem { @@ -602,7 +606,7 @@ func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFlo 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)) + items.append(InstantPageImageItem(frame: imageItem.frame.offsetBy(dx: offset.x, dy: offset.y), webPage: webpage, media: InstantPageMedia(index: -1, media: image, url: nil, caption: nil, credit: nil), interactive: false, roundCorners: false, fit: false)) } } } diff --git a/TelegramUI/InstantPageTheme.swift b/TelegramUI/InstantPageTheme.swift index cdd74ee0dc..0b5a3e4d29 100644 --- a/TelegramUI/InstantPageTheme.swift +++ b/TelegramUI/InstantPageTheme.swift @@ -38,6 +38,7 @@ enum InstantPageTextCategoryType { case subheader case paragraph case caption + case credit case table case article } @@ -48,6 +49,7 @@ struct InstantPageTextCategories { let subheader: InstantPageTextAttributes let paragraph: InstantPageTextAttributes let caption: InstantPageTextAttributes + let credit: InstantPageTextAttributes let table: InstantPageTextAttributes let article: InstantPageTextAttributes @@ -63,6 +65,8 @@ struct InstantPageTextCategories { return self.paragraph.withUnderline(link) case .caption: return self.caption.withUnderline(link) + case .credit: + return self.credit.withUnderline(link) case .table: return self.table.withUnderline(link) case .article: @@ -71,7 +75,7 @@ struct InstantPageTextCategories { } func withUpdatedFontStyles(sizeMultiplier: CGFloat, forceSerif: Bool) -> InstantPageTextCategories { - 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), article: self.article.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), credit: self.credit.withUpdatedFontStyles(sizeMultiplier: sizeMultiplier, forceSerif: forceSerif), table: self.table.withUpdatedFontStyles(sizeMultiplier: sizeMultiplier, forceSerif: forceSerif), article: self.article.withUpdatedFontStyles(sizeMultiplier: sizeMultiplier, forceSerif: forceSerif)) } } @@ -132,6 +136,7 @@ private let lightTheme = InstantPageTheme( 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: 15.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0x79828b)), + credit: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 13.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0x79828b)), table: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 1.0), color: .black), article: InstantPageTextAttributes(font: InstantPageFont(style: .serif, size: 17.0, lineSpacingFactor: 1.0), color: .black) ), @@ -159,6 +164,7 @@ private let sepiaTheme = InstantPageTheme( 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: 15.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0x927e6b)), + credit: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 13.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0x927e6b)), table: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0x4f321d)), article: InstantPageTextAttributes(font: InstantPageFont(style: .serif, size: 17.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0x4f321d)) ), @@ -186,6 +192,7 @@ private let grayTheme = InstantPageTheme( 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: 15.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0xa0a0a0)), + credit: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 13.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0xa0a0a0)), table: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0xcecece)), article: InstantPageTextAttributes(font: InstantPageFont(style: .serif, size: 17.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0xcecece)) ), @@ -213,6 +220,7 @@ private let darkTheme = InstantPageTheme( 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: 15.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0x6a6a6a)), + credit: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 13.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0x6a6a6a)), table: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0xb0b0b0)), article: InstantPageTextAttributes(font: InstantPageFont(style: .serif, size: 17.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0xb0b0b0)) ), diff --git a/TelegramUI/InstantPageTile.swift b/TelegramUI/InstantPageTile.swift index c8ce9afd13..ac991e2f01 100644 --- a/TelegramUI/InstantPageTile.swift +++ b/TelegramUI/InstantPageTile.swift @@ -9,8 +9,19 @@ final class InstantPageTile { self.frame = frame } + func getRandomColor() -> UIColor { + //Generate between 0 to 1 + let red:CGFloat = CGFloat(drand48()) + let green:CGFloat = CGFloat(drand48()) + let blue:CGFloat = CGFloat(drand48()) + + return UIColor(red:red, green: green, blue: blue, alpha: 1.0) + } + func draw(context: CGContext) { context.translateBy(x: -self.frame.minX, y: -self.frame.minY) + //context.setFillColor(getRandomColor().cgColor) + //context.fill(self.frame) for item in self.items { item.drawInTile(context: context) } diff --git a/TelegramUI/InstantPageWebEmbedItem.swift b/TelegramUI/InstantPageWebEmbedItem.swift index b1eb891409..1e76f735b6 100644 --- a/TelegramUI/InstantPageWebEmbedItem.swift +++ b/TelegramUI/InstantPageWebEmbedItem.swift @@ -6,7 +6,7 @@ import AsyncDisplayKit final class InstantPageWebEmbedItem: InstantPageItem { var frame: CGRect let wantsNode: Bool = true - let separatesTiles: Bool = true + let separatesTiles: Bool = false let medias: [InstantPageMedia] = [] let url: String? diff --git a/TelegramUI/LanguageSuggestionController.swift b/TelegramUI/LanguageSuggestionController.swift new file mode 100644 index 0000000000..d88e5958d0 --- /dev/null +++ b/TelegramUI/LanguageSuggestionController.swift @@ -0,0 +1,361 @@ +import Foundation +import SwiftSignalKit +import AsyncDisplayKit +import Display +import TelegramCore + +struct LanguageSuggestionControllerStrings { + let ChooseLanguage: String + let Other: String + let English: String + + init(localization: SuggestedLocalizationInfo) { + var chooseLanguage = "Choose Your Language" + var other = "Other" + var english = "English" + + for entry in localization.extractedEntries { + switch entry { + case let .string(key, value): + switch key { + case "Localization.ChooseLanguage": + chooseLanguage = value + case "Localization.LanguageOther": + other = value + case "Localization.EnglishLanguageName": + english = value + default: + break + } + default: + break + } + } + + self.ChooseLanguage = chooseLanguage + self.Other = other + self.English = english + } + + init(bundle: Bundle?) { + var chooseLanguage = "Choose Your Language" + var other = "Other" + var english = "English" + + if let bundle = bundle { + for key in LanguageSuggestionControllerStrings.keys { + let value = bundle.localizedString(forKey: key, value: nil, table: nil) + if value != key { + switch key { + case "Localization.ChooseLanguage": + chooseLanguage = value + case "Localization.LanguageOther": + other = value + case "Localization.EnglishLanguageName": + english = value + default: + break + } + } + } + } + + self.ChooseLanguage = chooseLanguage + self.Other = other + self.English = english + } + + static let keys: [String] = ["Localization.ChooseLanguage", + "Localization.LanguageOther", + "Localization.EnglishLanguageName"] +} + +private enum LanguageSuggestionItemType { + case localization(String) + case disclosure + case action +} + +private struct LanguageSuggestionItem { + public let type: LanguageSuggestionItemType + public let title: String + public let subtitle: String? + public let action: () -> Void + + public init(type: LanguageSuggestionItemType, title: String, subtitle: String?, action: @escaping () -> Void) { + self.type = type + self.title = title + self.subtitle = subtitle + self.action = action + } +} + +private final class LanguageSuggestionItemNode: HighlightableButtonNode { + private let backgroundNode: ASDisplayNode + private let separatorNode: ASDisplayNode + private let subtitleNode: ASTextNode + private let iconNode: ASImageNode + + let item: LanguageSuggestionItem + + override var isSelected: Bool { + didSet { + if case .localization = self.item.type { + self.iconNode.isHidden = !self.isSelected + } + } + } + + init(theme: PresentationTheme, item: LanguageSuggestionItem) { + self.item = item + + self.backgroundNode = ASDisplayNode() + self.backgroundNode.isLayerBacked = true + self.backgroundNode.backgroundColor = theme.actionSheet.opaqueItemHighlightedBackgroundColor + self.backgroundNode.alpha = 0.0 + + self.separatorNode = ASDisplayNode() + self.separatorNode.backgroundColor = theme.actionSheet.opaqueItemSeparatorColor + + self.subtitleNode = ASTextNode() + + self.iconNode = ASImageNode() + + super.init() + + self.addSubnode(self.subtitleNode) + self.addSubnode(self.separatorNode) + self.addSubnode(self.iconNode) + + var color: UIColor = theme.actionSheet.primaryTextColor + var alignment: ASHorizontalAlignment = .left + var inset: CGFloat = 19.0 + var icon: UIImage? + switch item.type { + case .action: + alignment = .middle + color = theme.actionSheet.controlAccentColor + inset = 0.0 + case .disclosure: + icon = PresentationResourcesItemList.disclosureArrowImage(theme) + case .localization: + icon = PresentationResourcesItemList.checkIconImage(theme) + } + + self.iconNode.image = icon + self.contentHorizontalAlignment = alignment + self.setTitle(item.title, with: Font.regular(17.0), with: color, for: []) + + var titleVerticalOffset: CGFloat = 0.0 + if let subtitle = item.subtitle { + self.subtitleNode.attributedText = NSAttributedString(string: subtitle, font: Font.regular(14.0), textColor: theme.actionSheet.secondaryTextColor) + titleVerticalOffset = 20.0 + } + self.contentEdgeInsets = UIEdgeInsets(top: 0.0, left: inset, bottom: titleVerticalOffset, right: 0.0) + + self.highligthedChanged = { [weak self] value in + if let strongSelf = self { + if value { + if strongSelf.backgroundNode.supernode == nil { + strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0) + } + strongSelf.backgroundNode.layer.removeAnimation(forKey: "opacity") + strongSelf.backgroundNode.alpha = 1.0 + } else if !strongSelf.backgroundNode.alpha.isZero { + strongSelf.backgroundNode.alpha = 0.0 + strongSelf.backgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25) + } + } + } + } + + override func didLoad() { + super.didLoad() + + self.addTarget(self, action: #selector(self.pressed), forControlEvents: .touchUpInside) + } + + @objc func pressed() { + self.item.action() + } + + public func updateLayout(_ constrainedSize: CGSize) -> CGSize { + let bounds = CGRect(origin: CGPoint(), size: CGSize(width: constrainedSize.width, height: self.item.subtitle != nil ? 58.0 : 44.0)) + self.backgroundNode.frame = bounds + + let subtitleSize = self.subtitleNode.measure(bounds.size) + self.subtitleNode.frame = CGRect(origin: CGPoint(x: 19.0, y: 31.0), size: subtitleSize) + self.separatorNode.frame = CGRect(x: 0.0, y: bounds.height - UIScreenPixel, width: bounds.width, height: UIScreenPixel) + if let icon = self.iconNode.image { + self.iconNode.frame = CGRect(origin: CGPoint(x: bounds.width - icon.size.width - 19.0, y: floorToScreenPixels((bounds.height - icon.size.height) / 2.0)), size: icon.size) + } + return bounds.size + } +} + +private final class LanguageSuggestionAlertContentNode: AlertContentNode { + private var validLayout: CGSize? + + private let titleNode: ASTextNode + private let subtitleNode: ASTextNode + private let titleSeparatorNode: ASDisplayNode + private let activityIndicator: ActivityIndicator + + private var nodes: [LanguageSuggestionItemNode] + + private let disposable = MetaDisposable() + + override var dismissOnOutsideTap: Bool { + return self.isUserInteractionEnabled + } + + init(theme: PresentationTheme, strings: LanguageSuggestionControllerStrings, englishStrings: LanguageSuggestionControllerStrings, suggestedLocalization: LocalizationInfo, openSelection: @escaping () -> Void, applyLocalization: @escaping (String, () -> Void) -> Void, dismiss: @escaping () -> Void) { + let selectedLocalization = ValuePromise(suggestedLocalization.languageCode, ignoreRepeated: true) + + self.titleNode = ASTextNode() + self.titleNode.attributedText = NSAttributedString(string: strings.ChooseLanguage, font: Font.bold(17.0), textColor: theme.actionSheet.primaryTextColor, paragraphAlignment: .center) + self.titleNode.maximumNumberOfLines = 2 + + self.subtitleNode = ASTextNode() + self.subtitleNode.attributedText = NSAttributedString(string: englishStrings.ChooseLanguage, font: Font.regular(14.0), textColor: theme.actionSheet.secondaryTextColor, paragraphAlignment: .center) + self.subtitleNode.maximumNumberOfLines = 2 + + self.titleSeparatorNode = ASDisplayNode() + self.titleSeparatorNode.backgroundColor = theme.actionSheet.opaqueItemSeparatorColor + + self.activityIndicator = ActivityIndicator(type: .custom(theme.actionSheet.controlAccentColor, 22.0, 1.0, false)) + self.activityIndicator.isHidden = true + + var items: [LanguageSuggestionItem] = [] + items.append(LanguageSuggestionItem(type: .localization(suggestedLocalization.languageCode), title: suggestedLocalization.localizedTitle, subtitle: suggestedLocalization.title, action: { + selectedLocalization.set(suggestedLocalization.languageCode) + })) + items.append(LanguageSuggestionItem(type: .localization("en"), title: strings.English, subtitle: englishStrings.English, action: { + selectedLocalization.set("en") + })) + items.append(LanguageSuggestionItem(type: .disclosure, title: strings.Other, subtitle: englishStrings.Other != strings.Other ? englishStrings.Other : nil, action: { + openSelection() + })) + + var applyImpl: (() -> Void)? + items.append(LanguageSuggestionItem(type: .action, title: "OK", subtitle: nil, action: { + applyImpl?() + })) + + self.nodes = items.map { LanguageSuggestionItemNode(theme: theme, item: $0) } + + super.init() + + self.addSubnode(self.titleNode) + self.addSubnode(self.subtitleNode) + self.addSubnode(self.titleSeparatorNode) + self.addSubnode(self.activityIndicator) + for node in self.nodes { + self.addSubnode(node) + } + + self.disposable.set(selectedLocalization.get().start(next: { [weak self] selectedCode in + if let strongSelf = self { + for node in strongSelf.nodes { + if case let .localization(code) = node.item.type { + node.isSelected = code == selectedCode + } + } + } + })) + + applyImpl = { [weak self] in + if let strongSelf = self { + strongSelf.isUserInteractionEnabled = false + + _ = (selectedLocalization.get() + |> take(1)).start(next: { selectedCode in + applyLocalization(selectedCode, { [weak self] in + if let strongSelf = self { + strongSelf.activityIndicator.isHidden = false + if let lastNode = strongSelf.nodes.last { + lastNode.isHidden = true + } + } + }) + }) + } + } + } + + deinit { + self.disposable.dispose() + } + + override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { + self.validLayout = size + + var origin: CGPoint = CGPoint(x: 0.0, y: 17.0) + + let titleSize = self.titleNode.measure(size) + transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: origin.y), size: titleSize)) + origin.y += titleSize.height + 3.0 + + let subtitleSize = self.subtitleNode.measure(size) + transition.updateFrame(node: self.subtitleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - subtitleSize.width) / 2.0), y: origin.y), size: subtitleSize)) + origin.y += subtitleSize.height + 17.0 + transition.updateFrame(node: self.titleSeparatorNode, frame: CGRect(x: 0.0, y: origin.y - UIScreenPixel, width: size.width, height: UIScreenPixel)) + + var lastNodeSize: CGSize? + for node in self.nodes { + let size = node.updateLayout(size) + transition.updateFrame(node: node, frame: CGRect(origin: origin, size: size)) + origin.y += size.height + lastNodeSize = size + } + + if let lastSize = lastNodeSize { + let indicatorSize = self.activityIndicator.measure(CGSize(width: 100.0, height: 100.0)) + transition.updateFrame(node: self.activityIndicator, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - indicatorSize.width) / 2.0), y: origin.y - lastSize.height + floorToScreenPixels((lastSize.height - indicatorSize.height) / 2.0)), size: indicatorSize)) + } + + return CGSize(width: size.width, height: origin.y - UIScreenPixel) + } +} + +func languageSuggestionController(account: Account, suggestedLocalization: SuggestedLocalizationInfo, currentLanguageCode: String, openSelection: @escaping () -> Void) -> AlertController? { + guard let localization = suggestedLocalization.availableLocalizations.filter({ $0.languageCode == suggestedLocalization.languageCode }).first else { + return nil + } + + let theme = account.telegramApplicationContext.currentPresentationData.with { $0 }.theme + let strings = LanguageSuggestionControllerStrings(localization: suggestedLocalization) + guard let mainPath = Bundle.main.path(forResource: "en", ofType: "lproj") else { + return nil + } + let englishStrings = LanguageSuggestionControllerStrings(bundle: Bundle(path: mainPath)) + + let disposable = MetaDisposable() + + var dismissImpl: ((Bool) -> Void)? + let contentNode = LanguageSuggestionAlertContentNode(theme: theme, strings: strings, englishStrings: englishStrings, suggestedLocalization: localization, openSelection: { + dismissImpl?(false) + openSelection() + }, applyLocalization: { languageCode, startActivity in + if languageCode == currentLanguageCode { + dismissImpl?(true) + } else { + startActivity() + disposable.set((downloadAndApplyLocalization(postbox: account.postbox, network: account.network, languageCode: languageCode) + |> deliverOnMainQueue).start(completed: { + dismissImpl?(true) + })) + } + }, dismiss: { + dismissImpl?(true) + }) + let controller = AlertController(theme: AlertControllerTheme(presentationTheme: theme), contentNode: contentNode) + dismissImpl = { [weak controller] animated in + if animated { + controller?.dismissAnimated() + } else { + controller?.dismiss() + } + } + return controller +} diff --git a/TelegramUI/ListMessageFileItemNode.swift b/TelegramUI/ListMessageFileItemNode.swift index aeed7afc33..9603c02aa2 100644 --- a/TelegramUI/ListMessageFileItemNode.swift +++ b/TelegramUI/ListMessageFileItemNode.swift @@ -116,7 +116,7 @@ private struct FetchControls { private enum FileIconImage: Equatable { case imageRepresentation(TelegramMediaFile, TelegramMediaImageRepresentation) - case albumArt(SharedMediaPlaybackAlbumArt) + case albumArt(TelegramMediaFile, SharedMediaPlaybackAlbumArt) static func ==(lhs: FileIconImage, rhs: FileIconImage) -> Bool { switch lhs { @@ -126,8 +126,8 @@ private enum FileIconImage: Equatable { } else { return false } - case let .albumArt(value): - if case .albumArt(value) = rhs { + case let .albumArt(file, value): + if case .albumArt(file, value) = rhs { return true } else { return false @@ -342,7 +342,7 @@ final class ListMessageFileItemNode: ListMessageNode { descriptionText = NSAttributedString(string: descriptionString, font: descriptionFont, textColor: item.theme.list.itemSecondaryTextColor) if !voice { - iconImage = .albumArt(SharedMediaPlaybackAlbumArt(thumbnailResource: ExternalMusicAlbumArtResource(title: title ?? "", performer: performer ?? "", isThumbnail: true), fullSizeResource: ExternalMusicAlbumArtResource(title: title ?? "", performer: performer ?? "", isThumbnail: false))) + iconImage = .albumArt(file, SharedMediaPlaybackAlbumArt(thumbnailResource: ExternalMusicAlbumArtResource(title: title ?? "", performer: performer ?? "", isThumbnail: true), fullSizeResource: ExternalMusicAlbumArtResource(title: title ?? "", performer: performer ?? "", isThumbnail: false))) } } } @@ -458,8 +458,8 @@ final class ListMessageFileItemNode: ListMessageNode { switch iconImage { case let .imageRepresentation(file, representation): updateIconImageSignal = chatWebpageSnippetFile(account: item.account, fileReference: .message(message: MessageReference(message), media: file), representation: representation) - case let .albumArt(albumArt): - updateIconImageSignal = playerAlbumArt(postbox: item.account.postbox, albumArt: albumArt, thumbnail: true) + case let .albumArt(file, albumArt): + updateIconImageSignal = playerAlbumArt(postbox: item.account.postbox, fileReference: .message(message: MessageReference(message), media: file), albumArt: albumArt, thumbnail: true) } } else { @@ -850,7 +850,7 @@ final class ListMessageFileItemNode: ListMessageNode { } @objc private func statusPressed() { - guard let item = self.item, let fetchStatus = self.fetchStatus else { + guard let _ = self.item, let fetchStatus = self.fetchStatus else { return } diff --git a/TelegramUI/OngoingCallContext.swift b/TelegramUI/OngoingCallContext.swift index 545ab30efd..81dc802598 100644 --- a/TelegramUI/OngoingCallContext.swift +++ b/TelegramUI/OngoingCallContext.swift @@ -97,6 +97,17 @@ private func ongoingNetworkTypeForType(_ type: NetworkType) -> OngoingCallNetwor } } +private func ongoingDataSavingForType(_ type: VoiceCallDataSaving) -> OngoingCallDataSaving { + switch type { + case .never: + return .never + case .cellular: + return .cellular + case .always: + return .always + } +} + final class OngoingCallContext { let internalId: CallSessionInternalId @@ -125,7 +136,7 @@ final class OngoingCallContext { private let audioSessionDisposable = MetaDisposable() private var networkTypeDisposable: Disposable? - init(account: Account, callSessionManager: CallSessionManager, internalId: CallSessionInternalId, proxyServer: ProxyServerSettings?, initialNetworkType: NetworkType, updatedNetworkType: Signal, serializedData: String?) { + init(account: Account, callSessionManager: CallSessionManager, internalId: CallSessionInternalId, proxyServer: ProxyServerSettings?, initialNetworkType: NetworkType, updatedNetworkType: Signal, serializedData: String?, dataSaving: VoiceCallDataSaving) { let _ = setupLogs OngoingCallThreadLocalContext.applyServerConfig(serializedData) @@ -143,7 +154,7 @@ final class OngoingCallContext { break } } - let context = OngoingCallThreadLocalContext(queue: OngoingCallThreadLocalContextQueueImpl(queue: queue), proxy: voipProxyServer, networkType: ongoingNetworkTypeForType(initialNetworkType)) + let context = OngoingCallThreadLocalContext(queue: OngoingCallThreadLocalContextQueueImpl(queue: queue), proxy: voipProxyServer, networkType: ongoingNetworkTypeForType(initialNetworkType), dataSaving: ongoingDataSavingForType(dataSaving)) self.contextRef = Unmanaged.passRetained(context) context.stateChanged = { [weak self] state in self?.contextState.set(.single(state)) @@ -227,3 +238,4 @@ final class OngoingCallContext { return (poll |> then(.complete() |> delay(0.5, queue: Queue.concurrentDefaultQueue()))) |> restart } } + diff --git a/TelegramUI/OngoingCallThreadLocalContext.h b/TelegramUI/OngoingCallThreadLocalContext.h index 7a570d853d..66985d6a33 100644 --- a/TelegramUI/OngoingCallThreadLocalContext.h +++ b/TelegramUI/OngoingCallThreadLocalContext.h @@ -29,6 +29,12 @@ typedef NS_ENUM(int32_t, OngoingCallNetworkType) { OngoingCallNetworkTypeCellularLte }; +typedef NS_ENUM(int32_t, OngoingCallDataSaving) { + OngoingCallDataSavingNever, + OngoingCallDataSavingCellular, + OngoingCallDataSavingAlways +}; + @protocol OngoingCallThreadLocalContextQueue - (void)dispatch:(void (^ _Nonnull)())f; @@ -55,7 +61,7 @@ typedef NS_ENUM(int32_t, OngoingCallNetworkType) { @property (nonatomic, copy) void (^ _Nullable stateChanged)(OngoingCallState); @property (nonatomic, copy) void (^ _Nullable callEnded)(NSString * _Nullable debugLog, int64_t bytesSentWifi, int64_t bytesReceivedWifi, int64_t bytesSentMobile, int64_t bytesReceivedMobile); -- (instancetype _Nonnull)initWithQueue:(id _Nonnull)queue proxy:(VoipProxyServer * _Nullable)proxy networkType:(OngoingCallNetworkType)networkType; +- (instancetype _Nonnull)initWithQueue:(id _Nonnull)queue proxy:(VoipProxyServer * _Nullable)proxy networkType:(OngoingCallNetworkType)networkType dataSaving:(OngoingCallDataSaving)dataSaving; - (void)startWithKey:(NSData * _Nonnull)key isOutgoing:(bool)isOutgoing primaryConnection:(OngoingCallConnectionDescription * _Nonnull)primaryConnection alternativeConnections:(NSArray * _Nonnull)alternativeConnections maxLayer:(int32_t)maxLayer allowP2P:(BOOL)allowP2P; - (void)stop; diff --git a/TelegramUI/OngoingCallThreadLocalContext.mm b/TelegramUI/OngoingCallThreadLocalContext.mm index b097fd7127..c91358c418 100644 --- a/TelegramUI/OngoingCallThreadLocalContext.mm +++ b/TelegramUI/OngoingCallThreadLocalContext.mm @@ -175,6 +175,19 @@ static int callControllerNetworkTypeForType(OngoingCallNetworkType type) { } } +static int callControllerDataSavingForType(OngoingCallDataSaving type) { + switch (type) { + case OngoingCallDataSavingNever: + return tgvoip::DATA_SAVING_NEVER; + case OngoingCallDataSavingCellular: + return tgvoip::DATA_SAVING_MOBILE; + case OngoingCallDataSavingAlways: + return tgvoip::DATA_SAVING_ALWAYS; + default: + return tgvoip::DATA_SAVING_NEVER; + } +} + @implementation OngoingCallThreadLocalContext + (void)setupLoggingFunction:(void (*)(NSString *))loggingFunction { @@ -211,7 +224,7 @@ static int callControllerNetworkTypeForType(OngoingCallNetworkType type) { } } -- (instancetype _Nonnull)initWithQueue:(id _Nonnull)queue proxy:(VoipProxyServer * _Nullable)proxy networkType:(OngoingCallNetworkType)networkType { +- (instancetype _Nonnull)initWithQueue:(id _Nonnull)queue proxy:(VoipProxyServer * _Nullable)proxy networkType:(OngoingCallNetworkType)networkType dataSaving:(OngoingCallDataSaving)dataSaving { self = [super init]; if (self != nil) { _queue = queue; @@ -222,7 +235,7 @@ static int callControllerNetworkTypeForType(OngoingCallNetworkType type) { _callRingTimeout = 90.0; _callConnectTimeout = 30.0; _callPacketTimeout = 10.0; - _dataSavingMode = 0; + _dataSavingMode = callControllerDataSavingForType(dataSaving); _networkType = networkType; _controller = new tgvoip::VoIPController(); diff --git a/TelegramUI/OpenAddContact.swift b/TelegramUI/OpenAddContact.swift new file mode 100644 index 0000000000..225d756352 --- /dev/null +++ b/TelegramUI/OpenAddContact.swift @@ -0,0 +1,20 @@ +import Foundation +import SwiftSignalKit +import TelegramCore +import Display + +func openAddContact(account: Account, firstName: String = "", lastName: String = "", phoneNumber: String, label: String = "_$!!$_", present: @escaping (ViewController, Any?) -> Void, completed: @escaping () -> Void = {}) { + let _ = (DeviceAccess.contacts + |> take(1) + |> deliverOnMainQueue).start(next: { value in + if let value = value, value { + let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: firstName, lastName: lastName, phoneNumbers: [DeviceContactPhoneNumberData(label: label, value: phoneNumber)]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: []) + present(deviceContactInfoController(account: account, subject: .create(peer: nil, contactData: contactData, completion: { _, _,_ in }), completed: completed), ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + } else { + let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } + present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: presentationData.strings.AccessDenied_Title, text: presentationData.strings.Contacts_AccessDeniedError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: { + account.telegramApplicationContext.applicationBindings.openSettings() + })]), nil) + } + }) +} diff --git a/TelegramUI/OpenChatMessage.swift b/TelegramUI/OpenChatMessage.swift index 2f58c518d0..172a40df27 100644 --- a/TelegramUI/OpenChatMessage.swift +++ b/TelegramUI/OpenChatMessage.swift @@ -29,7 +29,7 @@ private func chatMessageGalleryControllerData(account: Account, message: Message switch action.action { case let .photoUpdated(image): if let peer = messageMainPeer(message), let image = image { - let promise: Promise<[AvatarGalleryEntry]> = Promise([AvatarGalleryEntry.image(image, nil)]) + let promise: Promise<[AvatarGalleryEntry]> = Promise([AvatarGalleryEntry.image(image, peer, message.timestamp, nil)]) let galleryController = AvatarGalleryController(account: account, peer: peer, remoteEntries: promise, replaceRootController: { controller, ready in }) diff --git a/TelegramUI/OverlayPlayerControllerNode.swift b/TelegramUI/OverlayPlayerControllerNode.swift index 0359ecba6a..2cc96e9d7f 100644 --- a/TelegramUI/OverlayPlayerControllerNode.swift +++ b/TelegramUI/OverlayPlayerControllerNode.swift @@ -67,6 +67,7 @@ final class OverlayPlayerControllerNode: ViewControllerTracingNode, UIGestureRec return false }, navigateToFirstDateMessage: { _ in }, requestRedeliveryOfFailedMessages: { _ in + }, addContact: { _ in }, requestMessageUpdate: { _ in }, cancelInteractiveKeyboardGestures: { }, automaticMediaDownloadSettings: AutomaticMediaDownloadSettings.defaultSettings) diff --git a/TelegramUI/OverlayPlayerControlsNode.swift b/TelegramUI/OverlayPlayerControlsNode.swift index 108d97b231..52b778ec1f 100644 --- a/TelegramUI/OverlayPlayerControlsNode.swift +++ b/TelegramUI/OverlayPlayerControlsNode.swift @@ -95,6 +95,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode { private var displayData: SharedMediaPlaybackDisplayData? private var currentAlbumArtInitialized = false private var currentAlbumArt: SharedMediaPlaybackAlbumArt? + private var currentFileReference: FileMediaReference? private var statusDisposable: Disposable? private var validLayout: (width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, maxHeight: CGFloat)? @@ -261,6 +262,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode { if let source = value?.item.playbackData?.source { switch source { case let .telegramFile(fileReference): + strongSelf.currentFileReference = fileReference if let size = fileReference.media.size { strongSelf.scrubberNode.bufferingStatus = postbox.mediaBox.resourceRangesStatus(fileReference.media.resource) |> map { ranges -> (IndexSet, Int) in @@ -338,9 +340,9 @@ final class OverlayPlayerControlsNode: ASDisplayNode { if self.currentAlbumArt != albumArt || !self.currentAlbumArtInitialized { self.currentAlbumArtInitialized = true self.currentAlbumArt = albumArt - self.albumArtNode.setSignal(playerAlbumArt(postbox: self.postbox, albumArt: albumArt, thumbnail: true)) + self.albumArtNode.setSignal(playerAlbumArt(postbox: self.postbox, fileReference: self.currentFileReference, albumArt: albumArt, thumbnail: true)) if let largeAlbumArtNode = self.largeAlbumArtNode { - largeAlbumArtNode.setSignal(playerAlbumArt(postbox: self.postbox, albumArt: albumArt, thumbnail: false)) + largeAlbumArtNode.setSignal(playerAlbumArt(postbox: self.postbox, fileReference: self.currentFileReference, albumArt: albumArt, thumbnail: false)) } } } @@ -397,7 +399,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode { self.largeAlbumArtNode = largeAlbumArtNode self.addSubnode(largeAlbumArtNode) if self.currentAlbumArtInitialized { - largeAlbumArtNode.setSignal(playerAlbumArt(postbox: self.postbox, albumArt: self.currentAlbumArt, thumbnail: false)) + largeAlbumArtNode.setSignal(playerAlbumArt(postbox: self.postbox, fileReference: self.currentFileReference, albumArt: self.currentAlbumArt, thumbnail: false)) } } diff --git a/TelegramUI/PeerAvatarImageGalleryItem.swift b/TelegramUI/PeerAvatarImageGalleryItem.swift index 05408b7acc..8f0b473e5f 100644 --- a/TelegramUI/PeerAvatarImageGalleryItem.swift +++ b/TelegramUI/PeerAvatarImageGalleryItem.swift @@ -55,23 +55,23 @@ private struct PeerAvatarImageGalleryThumbnailItem: GalleryThumbnailItem { class PeerAvatarImageGalleryItem: GalleryItem { let account: Account let peer: Peer - let strings: PresentationStrings + let presentationData: PresentationData let entry: AvatarGalleryEntry let delete: (() -> Void)? - init(account: Account, peer: Peer, strings: PresentationStrings, entry: AvatarGalleryEntry, delete: (() -> Void)?) { + init(account: Account, peer: Peer, presentationData: PresentationData, entry: AvatarGalleryEntry, delete: (() -> Void)?) { self.account = account self.peer = peer - self.strings = strings + self.presentationData = presentationData self.entry = entry self.delete = delete } func node() -> GalleryItemNode { - let node = PeerAvatarImageGalleryItemNode(account: self.account, peer: self.peer) + let node = PeerAvatarImageGalleryItemNode(account: self.account, presentationData: self.presentationData, peer: self.peer) if let indexData = self.entry.indexData { - node._title.set(.single("\(indexData.position + 1) \(self.strings.Common_of) \(indexData.totalCount)")) + node._title.set(.single("\(indexData.position + 1) \(self.presentationData.strings.Common_of) \(indexData.totalCount)")) } node.setEntry(self.entry) @@ -83,7 +83,7 @@ class PeerAvatarImageGalleryItem: GalleryItem { func updateNode(node: GalleryItemNode) { if let node = node as? PeerAvatarImageGalleryItemNode { if let indexData = self.entry.indexData { - node._title.set(.single("\(indexData.position + 1) \(self.strings.Common_of) \(indexData.totalCount)")) + node._title.set(.single("\(indexData.position + 1) \(self.presentationData.strings.Common_of) \(indexData.totalCount)")) } node.setEntry(self.entry) @@ -100,7 +100,7 @@ class PeerAvatarImageGalleryItem: GalleryItem { } else { return nil } - case let .image(image, _): + case let .image(image, _, _, _): content = .standaloneImage(image.representations) } @@ -125,12 +125,12 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode { private let statusDisposable = MetaDisposable() private var status: MediaResourceStatus? - init(account: Account, peer: Peer) { + init(account: Account, presentationData: PresentationData, peer: Peer) { self.account = account self.peer = peer self.imageNode = TransformImageNode() - self.footerContentNode = AvatarGalleryItemFooterContentNode(account: account) + self.footerContentNode = AvatarGalleryItemFooterContentNode(account: account, presentationData: presentationData) self.statusNodeContainer = HighlightableButtonNode() self.statusNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.5)) @@ -180,6 +180,8 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode { if self.entry != entry { self.entry = entry + self.footerContentNode.setEntry(entry) + if let largestSize = largestImageRepresentation(entry.representations) { let displaySize = largestSize.dimensions.fitted(CGSize(width: 1280.0, height: 1280.0)).dividedByScreenScale().integralFloor self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: displaySize, boundingSize: displaySize, intrinsicInsets: UIEdgeInsets()))() @@ -193,7 +195,7 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode { } else { representations = [] } - case let .image(image, _): + case let .image(image, _, _, _): representations = image.representations.map { representation in return (representation, .standalone(resource: representation.resource)) } @@ -372,7 +374,7 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode { } else { representations = [] } - case let .image(image, _): + case let .image(image, _, _, _): representations = image.representations.map { representation in return (representation, .standalone(resource: representation.resource)) } diff --git a/TelegramUI/PeerMediaCollectionController.swift b/TelegramUI/PeerMediaCollectionController.swift index de111b8e1a..6b77a758bf 100644 --- a/TelegramUI/PeerMediaCollectionController.swift +++ b/TelegramUI/PeerMediaCollectionController.swift @@ -232,6 +232,7 @@ public class PeerMediaCollectionController: TelegramController { return false }, navigateToFirstDateMessage: { _ in }, requestRedeliveryOfFailedMessages: { _ in + }, addContact: { _ in }, requestMessageUpdate: { _ in }, cancelInteractiveKeyboardGestures: { }, automaticMediaDownloadSettings: AutomaticMediaDownloadSettings.defaultSettings) diff --git a/TelegramUI/PeerSelectionControllerNode.swift b/TelegramUI/PeerSelectionControllerNode.swift index 69541a34a7..414ef0fbb6 100644 --- a/TelegramUI/PeerSelectionControllerNode.swift +++ b/TelegramUI/PeerSelectionControllerNode.swift @@ -203,7 +203,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { if let requestOpenMessageFromSearch = self?.requestOpenMessageFromSearch { requestOpenMessageFromSearch(peer, messageId) } - }), cancel: { [weak self] in + }, addContact: nil), cancel: { [weak self] in if let requestDeactivateSearch = self?.requestDeactivateSearch { requestDeactivateSearch() } diff --git a/TelegramUI/PhotoResources.swift b/TelegramUI/PhotoResources.swift index ac630ddeed..5dd2f9b038 100644 --- a/TelegramUI/PhotoResources.swift +++ b/TelegramUI/PhotoResources.swift @@ -631,7 +631,8 @@ public func chatMessagePhotoInternal(photoData: Signal<(Data?, Data?, Bool), NoE context.withFlippedContext { c in c.setBlendMode(.copy) if thumbnailImage == nil && fullSizeImage == nil { - c.setFillColor(UIColor.white.cgColor) + let color = arguments.emptyColor ?? UIColor.white + c.setFillColor(color.cgColor) c.fill(fittedRect) } else { if arguments.imageSize.width < arguments.boundingSize.width || arguments.imageSize.height < arguments.boundingSize.height { @@ -2347,10 +2348,16 @@ private func drawAlbumArtPlaceholder(into c: CGContext, arguments: TransformImag } } -func playerAlbumArt(postbox: Postbox, albumArt: SharedMediaPlaybackAlbumArt?, thumbnail: Bool) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { +func playerAlbumArt(postbox: Postbox, fileReference: FileMediaReference?, albumArt: SharedMediaPlaybackAlbumArt?, thumbnail: Bool) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { + + var fileThumbnailResource: TelegramMediaResource? + if let fileReference = fileReference, let smallestRepresentation = smallestImageRepresentation(fileReference.media.previewRepresentations) { + fileThumbnailResource = smallestRepresentation.resource + } + if let albumArt = albumArt { if thumbnail { - return albumArtThumbnailData(postbox: postbox, thumbnail: albumArt.thumbnailResource) |> map { thumbnailData in + return albumArtThumbnailData(postbox: postbox, thumbnail: fileThumbnailResource ?? albumArt.thumbnailResource) |> map { thumbnailData in return { arguments in let context = DrawingContext(size: arguments.drawingSize, clear: true) diff --git a/TelegramUI/PresentationCall.swift b/TelegramUI/PresentationCall.swift index 255912aad0..1a02d719b8 100644 --- a/TelegramUI/PresentationCall.swift +++ b/TelegramUI/PresentationCall.swift @@ -218,7 +218,7 @@ public final class PresentationCall { private var droppedCall = false private var dropCallKitCallTimer: SwiftSignalKit.Timer? - init(account: Account, audioSession: ManagedAudioSession, callSessionManager: CallSessionManager, callKitIntegration: CallKitIntegration?, serializedData: String?, getDeviceAccessData: @escaping () -> (presentationData: PresentationData, present: (ViewController, Any?) -> Void, openSettings: () -> Void), internalId: CallSessionInternalId, peerId: PeerId, isOutgoing: Bool, peer: Peer?, proxyServer: ProxyServerSettings?, currentNetworkType: NetworkType, updatedNetworkType: Signal) { + init(account: Account, audioSession: ManagedAudioSession, callSessionManager: CallSessionManager, callKitIntegration: CallKitIntegration?, serializedData: String?, dataSaving: VoiceCallDataSaving, getDeviceAccessData: @escaping () -> (presentationData: PresentationData, present: (ViewController, Any?) -> Void, openSettings: () -> Void), internalId: CallSessionInternalId, peerId: PeerId, isOutgoing: Bool, peer: Peer?, proxyServer: ProxyServerSettings?, currentNetworkType: NetworkType, updatedNetworkType: Signal) { self.account = account self.audioSession = audioSession self.callSessionManager = callSessionManager @@ -230,7 +230,7 @@ public final class PresentationCall { self.isOutgoing = isOutgoing self.peer = peer - self.ongoingContext = OngoingCallContext(account: account, callSessionManager: self.callSessionManager, internalId: self.internalId, proxyServer: proxyServer, initialNetworkType: currentNetworkType, updatedNetworkType: updatedNetworkType, serializedData: serializedData) + self.ongoingContext = OngoingCallContext(account: account, callSessionManager: self.callSessionManager, internalId: self.internalId, proxyServer: proxyServer, initialNetworkType: currentNetworkType, updatedNetworkType: updatedNetworkType, serializedData: serializedData, dataSaving: dataSaving) var didReceiveAudioOutputs = false self.sessionStateDisposable = (callSessionManager.callState(internalId: internalId) diff --git a/TelegramUI/PresentationCallManager.swift b/TelegramUI/PresentationCallManager.swift index cbc1048039..dc4969886d 100644 --- a/TelegramUI/PresentationCallManager.swift +++ b/TelegramUI/PresentationCallManager.swift @@ -219,7 +219,7 @@ public final class PresentationCallManager { private func ringingStatesUpdated(_ ringingStates: [(Peer, CallSessionRingingState, Bool)], currentNetworkType: NetworkType, enableCallKit: Bool) { if let firstState = ringingStates.first { if self.currentCall == nil { - let call = PresentationCall(account: self.account, audioSession: self.audioSession, callSessionManager: self.callSessionManager, callKitIntegration: enableCallKit ? self.callKitIntegration : nil, serializedData: self.callSettings?.1.serializedData, getDeviceAccessData: self.getDeviceAccessData, internalId: firstState.1.id, peerId: firstState.1.peerId, isOutgoing: false, peer: firstState.0, proxyServer: self.proxyServer, currentNetworkType: currentNetworkType, updatedNetworkType: self.networkType) + let call = PresentationCall(account: self.account, audioSession: self.audioSession, callSessionManager: self.callSessionManager, callKitIntegration: enableCallKit ? self.callKitIntegration : nil, serializedData: self.callSettings?.1.serializedData, dataSaving: self.callSettings?.0.dataSaving ?? .never, getDeviceAccessData: self.getDeviceAccessData, internalId: firstState.1.id, peerId: firstState.1.peerId, isOutgoing: false, peer: firstState.0, proxyServer: self.proxyServer, currentNetworkType: currentNetworkType, updatedNetworkType: self.networkType) self.currentCall = call self.currentCallPromise.set(.single(call)) self.hasActiveCallsPromise.set(true) @@ -318,7 +318,7 @@ public final class PresentationCallManager { currentCall.rejectBusy() } - let call = PresentationCall(account: strongSelf.account, audioSession: strongSelf.audioSession, callSessionManager: strongSelf.callSessionManager, callKitIntegration: callKitIntegrationIfEnabled(strongSelf.callKitIntegration, settings: strongSelf.callSettings?.0), serializedData: strongSelf.callSettings?.1.serializedData, getDeviceAccessData: strongSelf.getDeviceAccessData, internalId: internalId, peerId: peerId, isOutgoing: true, peer: nil, proxyServer: strongSelf.proxyServer, currentNetworkType: currentNetworkType, updatedNetworkType: strongSelf.networkType) + let call = PresentationCall(account: strongSelf.account, audioSession: strongSelf.audioSession, callSessionManager: strongSelf.callSessionManager, callKitIntegration: callKitIntegrationIfEnabled(strongSelf.callKitIntegration, settings: strongSelf.callSettings?.0), serializedData: strongSelf.callSettings?.1.serializedData, dataSaving: strongSelf.callSettings?.0.dataSaving ?? .never, getDeviceAccessData: strongSelf.getDeviceAccessData, internalId: internalId, peerId: peerId, isOutgoing: true, peer: nil, proxyServer: strongSelf.proxyServer, currentNetworkType: currentNetworkType, updatedNetworkType: strongSelf.networkType) strongSelf.currentCall = call strongSelf.currentCallPromise.set(.single(call)) strongSelf.hasActiveCallsPromise.set(true) diff --git a/TelegramUI/PresentationStrings.swift b/TelegramUI/PresentationStrings.swift index d36695954a..cfb3e40c93 100644 --- a/TelegramUI/PresentationStrings.swift +++ b/TelegramUI/PresentationStrings.swift @@ -1966,1109 +1966,1110 @@ public final class PresentationStrings { public var SocksProxySetup_Credentials: String { return self._s[1698]! } public var CallSettings_UseLessData: String { return self._s[1699]! } public var MediaPicker_GroupDescription: String { return self._s[1700]! } + public var Conversation_SendMessageErrorGroupRestricted: String { return self._s[1701]! } public func TwoStepAuth_EnterPasswordHint(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1701]!, self._r[1701]!, [_0]) + return formatWithArgumentRanges(self._s[1702]!, self._r[1702]!, [_0]) } - public var CallSettings_TabIcon: String { return self._s[1702]! } - public var ConversationProfile_UnknownAddMemberError: String { return self._s[1704]! } + public var CallSettings_TabIcon: String { return self._s[1703]! } + public var ConversationProfile_UnknownAddMemberError: String { return self._s[1705]! } public func Conversation_FileHowToText(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1707]!, self._r[1707]!, [_0]) + return formatWithArgumentRanges(self._s[1708]!, self._r[1708]!, [_0]) } - public var Channel_AdminLog_BanSendMedia: String { return self._s[1708]! } - public var Passport_Language_uz: String { return self._s[1709]! } - public var Watch_UserInfo_Unblock: String { return self._s[1710]! } - public var ChatSettings_AutoDownloadVideoMessages: String { return self._s[1712]! } - public var PrivacyPolicy_AgeVerificationTitle: String { return self._s[1713]! } - public var StickerPacksSettings_ArchivedMasks: String { return self._s[1715]! } - public var Message_Animation: String { return self._s[1717]! } - public var Checkout_PaymentMethod: String { return self._s[1718]! } - public var Channel_AdminLog_TitleSelectedEvents: String { return self._s[1719]! } - public var PrivacyPolicy_DeclineDeleteNow: String { return self._s[1720]! } - public var Privacy_Calls_NeverAllow_Title: String { return self._s[1721]! } - public var Cache_Music: String { return self._s[1722]! } - public var Settings_ProxyDisabled: String { return self._s[1726]! } - public var SocksProxySetup_Connecting: String { return self._s[1727]! } - public var Channel_Username_CreatePrivateLinkHelp: String { return self._s[1728]! } + public var Channel_AdminLog_BanSendMedia: String { return self._s[1709]! } + public var Passport_Language_uz: String { return self._s[1710]! } + public var Watch_UserInfo_Unblock: String { return self._s[1711]! } + public var ChatSettings_AutoDownloadVideoMessages: String { return self._s[1713]! } + public var PrivacyPolicy_AgeVerificationTitle: String { return self._s[1714]! } + public var StickerPacksSettings_ArchivedMasks: String { return self._s[1716]! } + public var Message_Animation: String { return self._s[1718]! } + public var Checkout_PaymentMethod: String { return self._s[1719]! } + public var Channel_AdminLog_TitleSelectedEvents: String { return self._s[1720]! } + public var PrivacyPolicy_DeclineDeleteNow: String { return self._s[1721]! } + public var Privacy_Calls_NeverAllow_Title: String { return self._s[1722]! } + public var Cache_Music: String { return self._s[1723]! } + public var Settings_ProxyDisabled: String { return self._s[1727]! } + public var SocksProxySetup_Connecting: String { return self._s[1728]! } + public var Channel_Username_CreatePrivateLinkHelp: String { return self._s[1729]! } public func Time_PreciseDate_m2(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1729]!, self._r[1729]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[1730]!, self._r[1730]!, [_1, _2, _3]) } public func FileSize_B(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1731]!, self._r[1731]!, [_0]) + return formatWithArgumentRanges(self._s[1732]!, self._r[1732]!, [_0]) } public func Target_ShareGameConfirmationGroup(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1734]!, self._r[1734]!, [_0]) + return formatWithArgumentRanges(self._s[1735]!, self._r[1735]!, [_0]) } - public var PhotoEditor_SaturationTool: String { return self._s[1736]! } - public var Channel_BanUser_BlockFor: String { return self._s[1737]! } - public var Call_StatusConnecting: String { return self._s[1738]! } - public var AutoNightTheme_NotAvailable: String { return self._s[1739]! } - public var PrivateDataSettings_Title: String { return self._s[1740]! } - public var Bot_Start: String { return self._s[1742]! } + public var PhotoEditor_SaturationTool: String { return self._s[1737]! } + public var Channel_BanUser_BlockFor: String { return self._s[1738]! } + public var Call_StatusConnecting: String { return self._s[1739]! } + public var AutoNightTheme_NotAvailable: String { return self._s[1740]! } + public var PrivateDataSettings_Title: String { return self._s[1741]! } + public var Bot_Start: String { return self._s[1743]! } public func Channel_AdminLog_MessageChangedGroupAbout(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1743]!, self._r[1743]!, [_0]) + return formatWithArgumentRanges(self._s[1744]!, self._r[1744]!, [_0]) } - public var Appearance_PreviewReplyAuthor: String { return self._s[1744]! } - public var Notifications_TextTone: String { return self._s[1745]! } - public var Settings_CallSettings: String { return self._s[1746]! } + public var Appearance_PreviewReplyAuthor: String { return self._s[1745]! } + public var Notifications_TextTone: String { return self._s[1746]! } + public var Settings_CallSettings: String { return self._s[1747]! } public func Watch_Time_ShortYesterdayAt(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1747]!, self._r[1747]!, [_0]) + return formatWithArgumentRanges(self._s[1748]!, self._r[1748]!, [_0]) } - public var Contacts_InviteToTelegram: String { return self._s[1748]! } + public var Contacts_InviteToTelegram: String { return self._s[1749]! } public func PINNED_DOC(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1749]!, self._r[1749]!, [_1]) + return formatWithArgumentRanges(self._s[1750]!, self._r[1750]!, [_1]) } - public var ChatSettings_PrivateChats: String { return self._s[1750]! } - public var DialogList_Draft: String { return self._s[1751]! } - public var Channel_EditAdmin_PermissionDeleteMessages: String { return self._s[1752]! } - public var Channel_BanUser_PermissionSendStickersAndGifs: String { return self._s[1753]! } - public var Conversation_CloudStorageInfo_Title: String { return self._s[1754]! } - public var Conversation_ClearSecretHistory: String { return self._s[1755]! } - public var Passport_Identity_EditIdentityCard: String { return self._s[1756]! } - public var Notification_RenamedChannel: String { return self._s[1757]! } - public var BlockedUsers_BlockUser: String { return self._s[1758]! } - public var ChatSettings_TextSize: String { return self._s[1759]! } - public var ChannelInfo_DeleteGroup: String { return self._s[1760]! } - public var PhoneNumberHelp_Alert: String { return self._s[1761]! } + public var ChatSettings_PrivateChats: String { return self._s[1751]! } + public var DialogList_Draft: String { return self._s[1752]! } + public var Channel_EditAdmin_PermissionDeleteMessages: String { return self._s[1753]! } + public var Channel_BanUser_PermissionSendStickersAndGifs: String { return self._s[1754]! } + public var Conversation_CloudStorageInfo_Title: String { return self._s[1755]! } + public var Conversation_ClearSecretHistory: String { return self._s[1756]! } + public var Passport_Identity_EditIdentityCard: String { return self._s[1757]! } + public var Notification_RenamedChannel: String { return self._s[1758]! } + public var BlockedUsers_BlockUser: String { return self._s[1759]! } + public var ChatSettings_TextSize: String { return self._s[1760]! } + public var ChannelInfo_DeleteGroup: String { return self._s[1761]! } + public var PhoneNumberHelp_Alert: String { return self._s[1762]! } public func PINNED_TEXT(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1762]!, self._r[1762]!, [_1, _2]) + return formatWithArgumentRanges(self._s[1763]!, self._r[1763]!, [_1, _2]) } - public var Watch_ChannelInfo_Title: String { return self._s[1763]! } - public var WebSearch_RecentSectionClear: String { return self._s[1764]! } - public var Channel_AdminLogFilter_AdminsAll: String { return self._s[1765]! } - public var Channel_Setup_TypePrivate: String { return self._s[1766]! } - public var PhotoEditor_TintTool: String { return self._s[1767]! } - public var Watch_Suggestion_CantTalk: String { return self._s[1768]! } - public var PhotoEditor_QualityHigh: String { return self._s[1769]! } - public var SocksProxySetup_AddProxyTitle: String { return self._s[1771]! } + public var Watch_ChannelInfo_Title: String { return self._s[1764]! } + public var WebSearch_RecentSectionClear: String { return self._s[1765]! } + public var Channel_AdminLogFilter_AdminsAll: String { return self._s[1766]! } + public var Channel_Setup_TypePrivate: String { return self._s[1767]! } + public var PhotoEditor_TintTool: String { return self._s[1768]! } + public var Watch_Suggestion_CantTalk: String { return self._s[1769]! } + public var PhotoEditor_QualityHigh: String { return self._s[1770]! } + public var SocksProxySetup_AddProxyTitle: String { return self._s[1772]! } public func CHAT_MESSAGE_STICKER(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1773]!, self._r[1773]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[1774]!, self._r[1774]!, [_1, _2, _3]) } - public var Map_ChooseAPlace: String { return self._s[1774]! } - public var Passport_Identity_NamePlaceholder: String { return self._s[1776]! } - public var Passport_ScanPassport: String { return self._s[1777]! } - public var Map_ShareLiveLocationHelp: String { return self._s[1778]! } - public var Watch_Bot_Restart: String { return self._s[1780]! } - public var Passport_RequestedInformation: String { return self._s[1781]! } - public var Channel_About_Help: String { return self._s[1782]! } - public var Web_OpenExternal: String { return self._s[1783]! } - public var Passport_Language_mn: String { return self._s[1784]! } - public var UserInfo_AddContact: String { return self._s[1786]! } - public var Privacy_ContactsSync: String { return self._s[1787]! } - public var SocksProxySetup_Connection: String { return self._s[1789]! } - public var Passport_NotLoggedInMessage: String { return self._s[1790]! } - public var Passport_PasswordPlaceholder: String { return self._s[1791]! } - public var Passport_PasswordCreate: String { return self._s[1792]! } - public var SocksProxySetup_ProxyStatusChecking: String { return self._s[1794]! } - public var Call_EncryptionKey_Title: String { return self._s[1795]! } - public var PhotoEditor_BlurToolLinear: String { return self._s[1797]! } - public var AuthSessions_EmptyText: String { return self._s[1798]! } - public var Notification_MessageLifetime1m: String { return self._s[1799]! } + public var Map_ChooseAPlace: String { return self._s[1775]! } + public var Passport_Identity_NamePlaceholder: String { return self._s[1777]! } + public var Passport_ScanPassport: String { return self._s[1778]! } + public var Map_ShareLiveLocationHelp: String { return self._s[1779]! } + public var Watch_Bot_Restart: String { return self._s[1781]! } + public var Passport_RequestedInformation: String { return self._s[1782]! } + public var Channel_About_Help: String { return self._s[1783]! } + public var Web_OpenExternal: String { return self._s[1784]! } + public var Passport_Language_mn: String { return self._s[1785]! } + public var UserInfo_AddContact: String { return self._s[1787]! } + public var Privacy_ContactsSync: String { return self._s[1788]! } + public var SocksProxySetup_Connection: String { return self._s[1790]! } + public var Passport_NotLoggedInMessage: String { return self._s[1791]! } + public var Passport_PasswordPlaceholder: String { return self._s[1792]! } + public var Passport_PasswordCreate: String { return self._s[1793]! } + public var SocksProxySetup_ProxyStatusChecking: String { return self._s[1795]! } + public var Call_EncryptionKey_Title: String { return self._s[1796]! } + public var PhotoEditor_BlurToolLinear: String { return self._s[1798]! } + public var AuthSessions_EmptyText: String { return self._s[1799]! } + public var Notification_MessageLifetime1m: String { return self._s[1800]! } public func Call_StatusBar(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1800]!, self._r[1800]!, [_0]) + return formatWithArgumentRanges(self._s[1801]!, self._r[1801]!, [_0]) } - public var EditProfile_NameAndPhotoHelp: String { return self._s[1801]! } - public var NotificationsSound_Tritone: String { return self._s[1802]! } - public var Passport_FieldAddressUploadHelp: String { return self._s[1803]! } - public var Month_ShortJuly: String { return self._s[1804]! } - public var CheckoutInfo_ShippingInfoAddress1Placeholder: String { return self._s[1805]! } - public var Watch_MessageView_ViewOnPhone: String { return self._s[1806]! } - public var CallSettings_Never: String { return self._s[1807]! } - public var Passport_Identity_TypeInternalPassportUploadScan: String { return self._s[1810]! } - public var TwoStepAuth_EmailSent: String { return self._s[1811]! } + public var EditProfile_NameAndPhotoHelp: String { return self._s[1802]! } + public var NotificationsSound_Tritone: String { return self._s[1803]! } + public var Passport_FieldAddressUploadHelp: String { return self._s[1804]! } + public var Month_ShortJuly: String { return self._s[1805]! } + public var CheckoutInfo_ShippingInfoAddress1Placeholder: String { return self._s[1806]! } + public var Watch_MessageView_ViewOnPhone: String { return self._s[1807]! } + public var CallSettings_Never: String { return self._s[1808]! } + public var Passport_Identity_TypeInternalPassportUploadScan: String { return self._s[1811]! } + public var TwoStepAuth_EmailSent: String { return self._s[1812]! } public func Notification_PinnedAnimationMessage(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1812]!, self._r[1812]!, [_0]) + return formatWithArgumentRanges(self._s[1813]!, self._r[1813]!, [_0]) } - public var TwoStepAuth_RecoveryTitle: String { return self._s[1815]! } - public var Notifications_MessageNotificationsExceptions: String { return self._s[1816]! } - public var WatchRemote_AlertOpen: String { return self._s[1818]! } - public var ExplicitContent_AlertChannel: String { return self._s[1819]! } - public var Notification_PassportValueEmail: String { return self._s[1820]! } - public var ContactInfo_PhoneLabelMobile: String { return self._s[1822]! } - public var Widget_AuthRequired: String { return self._s[1824]! } + public var TwoStepAuth_RecoveryTitle: String { return self._s[1816]! } + public var Notifications_MessageNotificationsExceptions: String { return self._s[1817]! } + public var WatchRemote_AlertOpen: String { return self._s[1819]! } + public var ExplicitContent_AlertChannel: String { return self._s[1820]! } + public var Notification_PassportValueEmail: String { return self._s[1821]! } + public var ContactInfo_PhoneLabelMobile: String { return self._s[1823]! } + public var Widget_AuthRequired: String { return self._s[1825]! } public func ForwardedAuthors2(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1825]!, self._r[1825]!, [_0, _1]) + return formatWithArgumentRanges(self._s[1826]!, self._r[1826]!, [_0, _1]) } - public var ChannelInfo_DeleteGroupConfirmation: String { return self._s[1826]! } - public var TwoStepAuth_ConfirmationText: String { return self._s[1827]! } - public var Login_SmsRequestState3: String { return self._s[1828]! } - public var Notifications_AlertTones: String { return self._s[1829]! } + public var ChannelInfo_DeleteGroupConfirmation: String { return self._s[1827]! } + public var TwoStepAuth_ConfirmationText: String { return self._s[1828]! } + public var Login_SmsRequestState3: String { return self._s[1829]! } + public var Notifications_AlertTones: String { return self._s[1830]! } public func Time_MonthOfYear_m10(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1830]!, self._r[1830]!, [_0]) + return formatWithArgumentRanges(self._s[1831]!, self._r[1831]!, [_0]) } - public var Login_InfoAvatarPhoto: String { return self._s[1832]! } - public var Calls_TabTitle: String { return self._s[1835]! } - public var Map_YouAreHere: String { return self._s[1836]! } - public var PhotoEditor_CurvesTool: String { return self._s[1837]! } - public var Map_LiveLocationFor1Hour: String { return self._s[1838]! } - public var AutoNightTheme_AutomaticSection: String { return self._s[1839]! } - public var Stickers_NoStickersFound: String { return self._s[1840]! } - public var Passport_Identity_AddIdentityCard: String { return self._s[1842]! } + public var Login_InfoAvatarPhoto: String { return self._s[1833]! } + public var Calls_TabTitle: String { return self._s[1836]! } + public var Map_YouAreHere: String { return self._s[1837]! } + public var PhotoEditor_CurvesTool: String { return self._s[1838]! } + public var Map_LiveLocationFor1Hour: String { return self._s[1839]! } + public var AutoNightTheme_AutomaticSection: String { return self._s[1840]! } + public var Stickers_NoStickersFound: String { return self._s[1841]! } + public var Passport_Identity_AddIdentityCard: String { return self._s[1843]! } public func Notification_JoinedChannel(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1843]!, self._r[1843]!, [_0]) + return formatWithArgumentRanges(self._s[1844]!, self._r[1844]!, [_0]) } - public var Passport_Language_et: String { return self._s[1844]! } - public var Passport_Language_en: String { return self._s[1845]! } - public var GroupInfo_ActionRestrict: String { return self._s[1848]! } - public var Checkout_ShippingOption_Title: String { return self._s[1849]! } - public var Stickers_SuggestStickers: String { return self._s[1851]! } + public var Passport_Language_et: String { return self._s[1845]! } + public var Passport_Language_en: String { return self._s[1846]! } + public var GroupInfo_ActionRestrict: String { return self._s[1849]! } + public var Checkout_ShippingOption_Title: String { return self._s[1850]! } + public var Stickers_SuggestStickers: String { return self._s[1852]! } public func Channel_AdminLog_MessageKickedName(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1855]!, self._r[1855]!, [_1]) + return formatWithArgumentRanges(self._s[1856]!, self._r[1856]!, [_1]) } - public var Conversation_EncryptionProcessing: String { return self._s[1856]! } - public var TwoStepAuth_EmailCodeExpired: String { return self._s[1857]! } + public var Conversation_EncryptionProcessing: String { return self._s[1857]! } + public var TwoStepAuth_EmailCodeExpired: String { return self._s[1858]! } public func CHAT_ADD_MEMBER(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1860]!, self._r[1860]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[1861]!, self._r[1861]!, [_1, _2, _3]) } - public var Weekday_ShortSunday: String { return self._s[1863]! } - public var Privacy_ContactsResetConfirmation: String { return self._s[1864]! } - public var Month_ShortJune: String { return self._s[1865]! } - public var Privacy_Calls_Integration: String { return self._s[1866]! } - public var Channel_TypeSetup_Title: String { return self._s[1867]! } - public var Month_GenApril: String { return self._s[1868]! } - public var StickerPacksSettings_ShowStickersButton: String { return self._s[1869]! } - public var CheckoutInfo_ShippingInfoTitle: String { return self._s[1870]! } - public var Notification_PassportValueProofOfAddress: String { return self._s[1871]! } - public var Weekday_Tuesday: String { return self._s[1872]! } - public var StickerPacksSettings_ShowStickersButtonHelp: String { return self._s[1873]! } + public var Weekday_ShortSunday: String { return self._s[1864]! } + public var Privacy_ContactsResetConfirmation: String { return self._s[1865]! } + public var Month_ShortJune: String { return self._s[1866]! } + public var Privacy_Calls_Integration: String { return self._s[1867]! } + public var Channel_TypeSetup_Title: String { return self._s[1868]! } + public var Month_GenApril: String { return self._s[1869]! } + public var StickerPacksSettings_ShowStickersButton: String { return self._s[1870]! } + public var CheckoutInfo_ShippingInfoTitle: String { return self._s[1871]! } + public var Notification_PassportValueProofOfAddress: String { return self._s[1872]! } + public var Weekday_Tuesday: String { return self._s[1873]! } + public var StickerPacksSettings_ShowStickersButtonHelp: String { return self._s[1874]! } public func Compatibility_SecretMediaVersionTooLow(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1874]!, self._r[1874]!, [_0, _1]) + return formatWithArgumentRanges(self._s[1875]!, self._r[1875]!, [_0, _1]) } - public var CallSettings_RecentCalls: String { return self._s[1875]! } + public var CallSettings_RecentCalls: String { return self._s[1876]! } public func Conversation_Megabytes(_ _0: Float) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1880]!, self._r[1880]!, ["\(_0)"]) + return formatWithArgumentRanges(self._s[1881]!, self._r[1881]!, ["\(_0)"]) } - public var Conversation_SearchByName_Prefix: String { return self._s[1883]! } - public var TwoStepAuth_FloodError: String { return self._s[1884]! } - public var Paint_Stickers: String { return self._s[1885]! } - public var Login_InvalidCountryCode: String { return self._s[1886]! } - public var Privacy_Calls_AlwaysAllow_Title: String { return self._s[1887]! } - public var Username_InvalidTooShort: String { return self._s[1888]! } + public var Conversation_SearchByName_Prefix: String { return self._s[1884]! } + public var TwoStepAuth_FloodError: String { return self._s[1885]! } + public var Paint_Stickers: String { return self._s[1886]! } + public var Login_InvalidCountryCode: String { return self._s[1887]! } + public var Privacy_Calls_AlwaysAllow_Title: String { return self._s[1888]! } + public var Username_InvalidTooShort: String { return self._s[1889]! } public func Settings_ApplyProxyAlert(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1889]!, self._r[1889]!, [_1, _2]) + return formatWithArgumentRanges(self._s[1890]!, self._r[1890]!, [_1, _2]) } - public var Weekday_ShortFriday: String { return self._s[1890]! } + public var Weekday_ShortFriday: String { return self._s[1891]! } public func Login_BannedPhoneBody(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1891]!, self._r[1891]!, [_0]) + return formatWithArgumentRanges(self._s[1892]!, self._r[1892]!, [_0]) } - public var Conversation_ClearAll: String { return self._s[1892]! } - public var Conversation_EditingMessageMediaChange: String { return self._s[1893]! } - public var Passport_FieldIdentityTranslationHelp: String { return self._s[1894]! } - public var Call_ReportIncludeLog: String { return self._s[1895]! } + public var Conversation_ClearAll: String { return self._s[1893]! } + public var Conversation_EditingMessageMediaChange: String { return self._s[1894]! } + public var Passport_FieldIdentityTranslationHelp: String { return self._s[1895]! } + public var Call_ReportIncludeLog: String { return self._s[1896]! } public func Time_MonthOfYear_m3(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1896]!, self._r[1896]!, [_0]) + return formatWithArgumentRanges(self._s[1897]!, self._r[1897]!, [_0]) } - public var SharedMedia_EmptyTitle: String { return self._s[1897]! } - public var Call_PhoneCallInProgressMessage: String { return self._s[1898]! } - public var Notification_GroupActivated: String { return self._s[1899]! } - public var Checkout_Name: String { return self._s[1900]! } - public var Passport_Address_PostcodePlaceholder: String { return self._s[1901]! } + public var SharedMedia_EmptyTitle: String { return self._s[1898]! } + public var Call_PhoneCallInProgressMessage: String { return self._s[1899]! } + public var Notification_GroupActivated: String { return self._s[1900]! } + public var Checkout_Name: String { return self._s[1901]! } + public var Passport_Address_PostcodePlaceholder: String { return self._s[1902]! } public func AUTH_REGION(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1902]!, self._r[1902]!, [_1, _2]) + return formatWithArgumentRanges(self._s[1903]!, self._r[1903]!, [_1, _2]) } - public var Settings_NotificationsAndSounds: String { return self._s[1903]! } - public var Conversation_EncryptionCanceled: String { return self._s[1904]! } + public var Settings_NotificationsAndSounds: String { return self._s[1904]! } + public var Conversation_EncryptionCanceled: String { return self._s[1905]! } public func GroupInfo_InvitationLinkAcceptChannel(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1905]!, self._r[1905]!, [_0]) + return formatWithArgumentRanges(self._s[1906]!, self._r[1906]!, [_0]) } - public var AccessDenied_SaveMedia: String { return self._s[1906]! } - public var InviteText_URL: String { return self._s[1907]! } - public var Passport_CorrectErrors: String { return self._s[1908]! } + public var AccessDenied_SaveMedia: String { return self._s[1907]! } + public var InviteText_URL: String { return self._s[1908]! } + public var Passport_CorrectErrors: String { return self._s[1909]! } public func Channel_AdminLog_MessageInvitedNameUsername(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1909]!, self._r[1909]!, [_1, _2]) + return formatWithArgumentRanges(self._s[1910]!, self._r[1910]!, [_1, _2]) } - public var Notifications_Badge_CountUnreadMessages: String { return self._s[1910]! } - public var Appearance_ReduceMotion: String { return self._s[1911]! } - public var Compose_GroupTokenListPlaceholder: String { return self._s[1912]! } - public var Passport_Address_CityPlaceholder: String { return self._s[1913]! } - public var Passport_InfoFAQ_URL: String { return self._s[1914]! } - public var Conversation_MessageDeliveryFailed: String { return self._s[1917]! } - public var Privacy_PaymentsClear_PaymentInfo: String { return self._s[1918]! } - public var Notifications_GroupNotifications: String { return self._s[1919]! } - public var CheckoutInfo_SaveInfoHelp: String { return self._s[1920]! } - public var Notification_Mute1hMin: String { return self._s[1921]! } - public var Privacy_TopPeersWarning: String { return self._s[1922]! } - public var StickerPacksSettings_ArchivedMasks_Info: String { return self._s[1924]! } - public var ChannelMembers_WhoCanAddMembers_AllMembers: String { return self._s[1926]! } - public var Channel_Edit_PrivatePublicLinkAlert: String { return self._s[1927]! } - public var Watch_Conversation_UserInfo: String { return self._s[1928]! } - public var Application_Name: String { return self._s[1929]! } - public var Conversation_AddToReadingList: String { return self._s[1930]! } - public var Conversation_FileDropbox: String { return self._s[1931]! } - public var Login_PhonePlaceholder: String { return self._s[1932]! } - public var SocksProxySetup_ProxyEnabled: String { return self._s[1933]! } - public var Profile_MessageLifetime1d: String { return self._s[1934]! } - public var CheckoutInfo_ShippingInfoCityPlaceholder: String { return self._s[1935]! } - public var Notifications_ChannelNotificationsSound: String { return self._s[1936]! } - public var Calls_CallTabDescription: String { return self._s[1937]! } - public var Passport_DeletePersonalDetails: String { return self._s[1938]! } - public var Passport_Address_AddBankStatement: String { return self._s[1939]! } - public var Resolve_ErrorNotFound: String { return self._s[1940]! } - public var Watch_Message_Call: String { return self._s[1941]! } - public var PhotoEditor_FadeTool: String { return self._s[1942]! } - public var Channel_Setup_TypePublicHelp: String { return self._s[1944]! } - public var GroupInfo_InviteLink_RevokeAlert_Success: String { return self._s[1947]! } - public var Channel_Setup_PublicNoLink: String { return self._s[1948]! } - public var Privacy_Calls_P2PHelp: String { return self._s[1949]! } - public var Conversation_Info: String { return self._s[1950]! } + public var Notifications_Badge_CountUnreadMessages: String { return self._s[1911]! } + public var Appearance_ReduceMotion: String { return self._s[1912]! } + public var Compose_GroupTokenListPlaceholder: String { return self._s[1913]! } + public var Passport_Address_CityPlaceholder: String { return self._s[1914]! } + public var Passport_InfoFAQ_URL: String { return self._s[1915]! } + public var Conversation_MessageDeliveryFailed: String { return self._s[1918]! } + public var Privacy_PaymentsClear_PaymentInfo: String { return self._s[1919]! } + public var Notifications_GroupNotifications: String { return self._s[1920]! } + public var CheckoutInfo_SaveInfoHelp: String { return self._s[1921]! } + public var Notification_Mute1hMin: String { return self._s[1922]! } + public var Privacy_TopPeersWarning: String { return self._s[1923]! } + public var StickerPacksSettings_ArchivedMasks_Info: String { return self._s[1925]! } + public var ChannelMembers_WhoCanAddMembers_AllMembers: String { return self._s[1927]! } + public var Channel_Edit_PrivatePublicLinkAlert: String { return self._s[1928]! } + public var Watch_Conversation_UserInfo: String { return self._s[1929]! } + public var Application_Name: String { return self._s[1930]! } + public var Conversation_AddToReadingList: String { return self._s[1931]! } + public var Conversation_FileDropbox: String { return self._s[1932]! } + public var Login_PhonePlaceholder: String { return self._s[1933]! } + public var SocksProxySetup_ProxyEnabled: String { return self._s[1934]! } + public var Profile_MessageLifetime1d: String { return self._s[1935]! } + public var CheckoutInfo_ShippingInfoCityPlaceholder: String { return self._s[1936]! } + public var Notifications_ChannelNotificationsSound: String { return self._s[1937]! } + public var Calls_CallTabDescription: String { return self._s[1938]! } + public var Passport_DeletePersonalDetails: String { return self._s[1939]! } + public var Passport_Address_AddBankStatement: String { return self._s[1940]! } + public var Resolve_ErrorNotFound: String { return self._s[1941]! } + public var Watch_Message_Call: String { return self._s[1942]! } + public var PhotoEditor_FadeTool: String { return self._s[1943]! } + public var Channel_Setup_TypePublicHelp: String { return self._s[1945]! } + public var GroupInfo_InviteLink_RevokeAlert_Success: String { return self._s[1948]! } + public var Channel_Setup_PublicNoLink: String { return self._s[1949]! } + public var Privacy_Calls_P2PHelp: String { return self._s[1950]! } + public var Conversation_Info: String { return self._s[1951]! } public func Time_TodayAt(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1952]!, self._r[1952]!, [_0]) + return formatWithArgumentRanges(self._s[1953]!, self._r[1953]!, [_0]) } - public var AutoDownloadSettings_VideosTitle: String { return self._s[1953]! } - public var Conversation_Processing: String { return self._s[1954]! } - public var Conversation_RestrictedInline: String { return self._s[1955]! } + public var AutoDownloadSettings_VideosTitle: String { return self._s[1954]! } + public var Conversation_Processing: String { return self._s[1955]! } + public var Conversation_RestrictedInline: String { return self._s[1956]! } public func InstantPage_AuthorAndDateTitle(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1959]!, self._r[1959]!, [_1, _2]) + return formatWithArgumentRanges(self._s[1960]!, self._r[1960]!, [_1, _2]) } public func Watch_LastSeen_AtDate(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1960]!, self._r[1960]!, [_0]) + return formatWithArgumentRanges(self._s[1961]!, self._r[1961]!, [_0]) } - public var Conversation_Location: String { return self._s[1961]! } - public var DialogList_PasscodeLockHelp: String { return self._s[1962]! } - public var Channel_Management_Title: String { return self._s[1963]! } - public var Notifications_InAppNotificationsPreview: String { return self._s[1964]! } - public var EnterPasscode_EnterTitle: String { return self._s[1966]! } - public var ReportPeer_ReasonOther_Title: String { return self._s[1967]! } - public var Month_GenJanuary: String { return self._s[1968]! } - public var Conversation_ForwardChats: String { return self._s[1969]! } - public var Channel_UpdatePhotoItem: String { return self._s[1971]! } - public var UserInfo_StartSecretChat: String { return self._s[1972]! } - public var PrivacySettings_LastSeenNobody: String { return self._s[1973]! } + public var Conversation_Location: String { return self._s[1962]! } + public var DialogList_PasscodeLockHelp: String { return self._s[1963]! } + public var Channel_Management_Title: String { return self._s[1964]! } + public var Notifications_InAppNotificationsPreview: String { return self._s[1965]! } + public var EnterPasscode_EnterTitle: String { return self._s[1967]! } + public var ReportPeer_ReasonOther_Title: String { return self._s[1968]! } + public var Month_GenJanuary: String { return self._s[1969]! } + public var Conversation_ForwardChats: String { return self._s[1970]! } + public var Channel_UpdatePhotoItem: String { return self._s[1972]! } + public var UserInfo_StartSecretChat: String { return self._s[1973]! } + public var PrivacySettings_LastSeenNobody: String { return self._s[1974]! } public func FileSize_MB(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1974]!, self._r[1974]!, [_0]) + return formatWithArgumentRanges(self._s[1975]!, self._r[1975]!, [_0]) } - public var ChatSearch_SearchPlaceholder: String { return self._s[1977]! } - public var TwoStepAuth_ConfirmationAbort: String { return self._s[1978]! } - public var FastTwoStepSetup_HintSection: String { return self._s[1979]! } - public var TwoStepAuth_SetupPasswordConfirmFailed: String { return self._s[1983]! } + public var ChatSearch_SearchPlaceholder: String { return self._s[1978]! } + public var TwoStepAuth_ConfirmationAbort: String { return self._s[1979]! } + public var FastTwoStepSetup_HintSection: String { return self._s[1980]! } + public var TwoStepAuth_SetupPasswordConfirmFailed: String { return self._s[1984]! } public func LastSeen_YesterdayAt(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1984]!, self._r[1984]!, [_0]) + return formatWithArgumentRanges(self._s[1985]!, self._r[1985]!, [_0]) } - public var GroupInfo_GroupHistoryVisible: String { return self._s[1986]! } - public var AppleWatch_ReplyPresetsHelp: String { return self._s[1987]! } - public var Localization_LanguageName: String { return self._s[1988]! } - public var Map_OpenIn: String { return self._s[1989]! } - public var Message_File: String { return self._s[1990]! } - public var Call_ReportSend: String { return self._s[1991]! } + public var GroupInfo_GroupHistoryVisible: String { return self._s[1987]! } + public var AppleWatch_ReplyPresetsHelp: String { return self._s[1988]! } + public var Localization_LanguageName: String { return self._s[1989]! } + public var Map_OpenIn: String { return self._s[1990]! } + public var Message_File: String { return self._s[1991]! } + public var Call_ReportSend: String { return self._s[1992]! } public func Channel_AdminLog_MessageChangedGroupUsername(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1992]!, self._r[1992]!, [_0]) + return formatWithArgumentRanges(self._s[1993]!, self._r[1993]!, [_0]) } public func CHAT_MESSAGE_GAME(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1993]!, self._r[1993]!, [_1, _2, _3]) - } - public func Time_PreciseDate_m1(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(self._s[1994]!, self._r[1994]!, [_1, _2, _3]) } - public var Month_ShortMay: String { return self._s[1997]! } - public var Tour_Text3: String { return self._s[1998]! } - public var Contacts_GlobalSearch: String { return self._s[1999]! } - public var DialogList_LanguageTooltip: String { return self._s[2000]! } - public var AuthSessions_LogOutApplications: String { return self._s[2001]! } - public var Map_LoadError: String { return self._s[2002]! } - public var Settings_ProxyConnecting: String { return self._s[2003]! } - public var Passport_Language_fa: String { return self._s[2005]! } - public var AccessDenied_VoiceMicrophone: String { return self._s[2006]! } + public func Time_PreciseDate_m1(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1995]!, self._r[1995]!, [_1, _2, _3]) + } + public var Month_ShortMay: String { return self._s[1998]! } + public var Tour_Text3: String { return self._s[1999]! } + public var Contacts_GlobalSearch: String { return self._s[2000]! } + public var DialogList_LanguageTooltip: String { return self._s[2001]! } + public var AuthSessions_LogOutApplications: String { return self._s[2002]! } + public var Map_LoadError: String { return self._s[2003]! } + public var Settings_ProxyConnecting: String { return self._s[2004]! } + public var Passport_Language_fa: String { return self._s[2006]! } + public var AccessDenied_VoiceMicrophone: String { return self._s[2007]! } public func CHANNEL_MESSAGE_STICKER(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2009]!, self._r[2009]!, [_1, _2]) + return formatWithArgumentRanges(self._s[2010]!, self._r[2010]!, [_1, _2]) } - public var Passport_Address_TypeUtilityBillUploadScan: String { return self._s[2010]! } - public var PrivacySettings_Title: String { return self._s[2011]! } - public var PasscodeSettings_TurnPasscodeOff: String { return self._s[2014]! } - public var MediaPicker_AddCaption: String { return self._s[2015]! } - public var Channel_AdminLog_BanReadMessages: String { return self._s[2016]! } - public var Channel_Status: String { return self._s[2017]! } - public var Map_ChooseLocationTitle: String { return self._s[2018]! } - public var Notifications_ChannelNotifications: String { return self._s[2019]! } - public var Map_OpenInYandexNavigator: String { return self._s[2020]! } - public var AutoNightTheme_PreferredTheme: String { return self._s[2021]! } - public var State_WaitingForNetwork: String { return self._s[2022]! } - public var TwoStepAuth_EmailHelp: String { return self._s[2023]! } - public var Conversation_StopLiveLocation: String { return self._s[2024]! } - public var Privacy_SecretChatsLinkPreviewsHelp: String { return self._s[2025]! } - public var PhotoEditor_SharpenTool: String { return self._s[2026]! } - public var Common_of: String { return self._s[2027]! } - public var AuthSessions_Title: String { return self._s[2028]! } - public var Passport_Scans_UploadNew: String { return self._s[2029]! } - public var Message_PinnedLiveLocationMessage: String { return self._s[2030]! } - public var Passport_FieldIdentityDetailsHelp: String { return self._s[2031]! } - public var PrivacyLastSeenSettings_AlwaysShareWith: String { return self._s[2032]! } - public var EnterPasscode_EnterPasscode: String { return self._s[2033]! } - public var Notifications_Reset: String { return self._s[2034]! } + public var Passport_Address_TypeUtilityBillUploadScan: String { return self._s[2011]! } + public var PrivacySettings_Title: String { return self._s[2012]! } + public var PasscodeSettings_TurnPasscodeOff: String { return self._s[2015]! } + public var MediaPicker_AddCaption: String { return self._s[2016]! } + public var Channel_AdminLog_BanReadMessages: String { return self._s[2017]! } + public var Channel_Status: String { return self._s[2018]! } + public var Map_ChooseLocationTitle: String { return self._s[2019]! } + public var Notifications_ChannelNotifications: String { return self._s[2020]! } + public var Map_OpenInYandexNavigator: String { return self._s[2021]! } + public var AutoNightTheme_PreferredTheme: String { return self._s[2022]! } + public var State_WaitingForNetwork: String { return self._s[2023]! } + public var TwoStepAuth_EmailHelp: String { return self._s[2024]! } + public var Conversation_StopLiveLocation: String { return self._s[2025]! } + public var Privacy_SecretChatsLinkPreviewsHelp: String { return self._s[2026]! } + public var PhotoEditor_SharpenTool: String { return self._s[2027]! } + public var Common_of: String { return self._s[2028]! } + public var AuthSessions_Title: String { return self._s[2029]! } + public var Passport_Scans_UploadNew: String { return self._s[2030]! } + public var Message_PinnedLiveLocationMessage: String { return self._s[2031]! } + public var Passport_FieldIdentityDetailsHelp: String { return self._s[2032]! } + public var PrivacyLastSeenSettings_AlwaysShareWith: String { return self._s[2033]! } + public var EnterPasscode_EnterPasscode: String { return self._s[2034]! } + public var Notifications_Reset: String { return self._s[2035]! } public func Map_LiveLocationPrivateDescription(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2035]!, self._r[2035]!, [_0]) + return formatWithArgumentRanges(self._s[2036]!, self._r[2036]!, [_0]) } - public var GroupInfo_InvitationLinkGroupFull: String { return self._s[2036]! } + public var GroupInfo_InvitationLinkGroupFull: String { return self._s[2037]! } public func Channel_AdminLog_MessageChangedChannelUsername(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2038]!, self._r[2038]!, [_0]) + return formatWithArgumentRanges(self._s[2039]!, self._r[2039]!, [_0]) } public func CHAT_MESSAGE_DOC(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2039]!, self._r[2039]!, [_1, _2]) + return formatWithArgumentRanges(self._s[2040]!, self._r[2040]!, [_1, _2]) } - public var Watch_AppName: String { return self._s[2040]! } - public var ConvertToSupergroup_HelpTitle: String { return self._s[2041]! } - public var Conversation_TapAndHoldToRecord: String { return self._s[2042]! } + public var Watch_AppName: String { return self._s[2041]! } + public var ConvertToSupergroup_HelpTitle: String { return self._s[2042]! } + public var Conversation_TapAndHoldToRecord: String { return self._s[2043]! } public func MESSAGE_GIF(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2044]!, self._r[2044]!, [_1]) + return formatWithArgumentRanges(self._s[2045]!, self._r[2045]!, [_1]) } public func DialogList_EncryptedChatStartedOutgoing(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2045]!, self._r[2045]!, [_0]) + return formatWithArgumentRanges(self._s[2046]!, self._r[2046]!, [_0]) } - public var Checkout_PayWithTouchId: String { return self._s[2046]! } - public var Passport_Language_ko: String { return self._s[2047]! } - public var Conversation_DiscardVoiceMessageTitle: String { return self._s[2048]! } + public var Checkout_PayWithTouchId: String { return self._s[2047]! } + public var Passport_Language_ko: String { return self._s[2048]! } + public var Conversation_DiscardVoiceMessageTitle: String { return self._s[2049]! } public func CHAT_ADD_YOU(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2049]!, self._r[2049]!, [_1, _2]) + return formatWithArgumentRanges(self._s[2050]!, self._r[2050]!, [_1, _2]) } - public var CheckoutInfo_ShippingInfoCity: String { return self._s[2050]! } - public var Group_AdminLog_EmptyText: String { return self._s[2051]! } - public var AutoDownloadSettings_GroupChats: String { return self._s[2052]! } - public var Conversation_ClousStorageInfo_Description3: String { return self._s[2054]! } - public var Notifications_ExceptionsMuted: String { return self._s[2055]! } - public var Conversation_PinMessageAlertGroup: String { return self._s[2056]! } - public var Settings_FAQ_Intro: String { return self._s[2058]! } - public var PrivacySettings_AuthSessions: String { return self._s[2059]! } + public var CheckoutInfo_ShippingInfoCity: String { return self._s[2051]! } + public var Group_AdminLog_EmptyText: String { return self._s[2052]! } + public var AutoDownloadSettings_GroupChats: String { return self._s[2053]! } + public var Conversation_ClousStorageInfo_Description3: String { return self._s[2055]! } + public var Notifications_ExceptionsMuted: String { return self._s[2056]! } + public var Conversation_PinMessageAlertGroup: String { return self._s[2057]! } + public var Settings_FAQ_Intro: String { return self._s[2059]! } + public var PrivacySettings_AuthSessions: String { return self._s[2060]! } public func CHAT_MESSAGE_GEOLIVE(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2060]!, self._r[2060]!, [_1, _2]) + return formatWithArgumentRanges(self._s[2061]!, self._r[2061]!, [_1, _2]) } - public var Passport_Address_Postcode: String { return self._s[2062]! } - public var Tour_Title5: String { return self._s[2063]! } - public var ChatAdmins_AllMembersAreAdmins: String { return self._s[2064]! } - public var Group_Management_AddModeratorHelp: String { return self._s[2065]! } - public var Channel_Username_CheckingUsername: String { return self._s[2066]! } + public var Passport_Address_Postcode: String { return self._s[2063]! } + public var Tour_Title5: String { return self._s[2064]! } + public var ChatAdmins_AllMembersAreAdmins: String { return self._s[2065]! } + public var Group_Management_AddModeratorHelp: String { return self._s[2066]! } + public var Channel_Username_CheckingUsername: String { return self._s[2067]! } public func DialogList_SingleRecordingVideoMessageSuffix(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2068]!, self._r[2068]!, [_0]) + return formatWithArgumentRanges(self._s[2069]!, self._r[2069]!, [_0]) } public func Contacts_AccessDeniedHelpPortrait(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2072]!, self._r[2072]!, [_0]) + return formatWithArgumentRanges(self._s[2073]!, self._r[2073]!, [_0]) } public func Checkout_LiabilityAlert(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2073]!, self._r[2073]!, [_1, _1, _1, _2]) + return formatWithArgumentRanges(self._s[2074]!, self._r[2074]!, [_1, _1, _1, _2]) } - public var Channel_Info_BlackList: String { return self._s[2074]! } - public var Profile_BotInfo: String { return self._s[2075]! } - public var Stickers_SuggestAll: String { return self._s[2076]! } - public var Compose_NewChannel_Members: String { return self._s[2077]! } - public var Notification_Reply: String { return self._s[2078]! } - public var Watch_Stickers_Recents: String { return self._s[2080]! } - public var GroupInfo_SetGroupPhotoStop: String { return self._s[2081]! } - public var Channel_Stickers_Placeholder: String { return self._s[2082]! } - public var AttachmentMenu_File: String { return self._s[2083]! } + public var Channel_Info_BlackList: String { return self._s[2075]! } + public var Profile_BotInfo: String { return self._s[2076]! } + public var Stickers_SuggestAll: String { return self._s[2077]! } + public var Compose_NewChannel_Members: String { return self._s[2078]! } + public var Notification_Reply: String { return self._s[2079]! } + public var Watch_Stickers_Recents: String { return self._s[2081]! } + public var GroupInfo_SetGroupPhotoStop: String { return self._s[2082]! } + public var Channel_Stickers_Placeholder: String { return self._s[2083]! } + public var AttachmentMenu_File: String { return self._s[2084]! } public func MESSAGE_STICKER(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2084]!, self._r[2084]!, [_1, _2]) + return formatWithArgumentRanges(self._s[2085]!, self._r[2085]!, [_1, _2]) } - public var Profile_MessageLifetime5s: String { return self._s[2085]! } - public var Privacy_ContactsReset: String { return self._s[2087]! } + public var Profile_MessageLifetime5s: String { return self._s[2086]! } + public var Privacy_ContactsReset: String { return self._s[2088]! } public func PINNED_PHOTO(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2088]!, self._r[2088]!, [_1]) + return formatWithArgumentRanges(self._s[2089]!, self._r[2089]!, [_1]) } - public var Channel_AdminLog_CanAddAdmins: String { return self._s[2091]! } - public var TwoStepAuth_SetupHint: String { return self._s[2092]! } - public var Conversation_StatusLeftGroup: String { return self._s[2093]! } - public var Settings_CopyUsername: String { return self._s[2094]! } - public var Passport_Identity_CountryPlaceholder: String { return self._s[2095]! } - public var ChatSettings_AutoDownloadDocuments: String { return self._s[2096]! } - public var MediaPicker_TapToUngroupDescription: String { return self._s[2097]! } - public var Conversation_ShareBotLocationConfirmation: String { return self._s[2098]! } - public var Conversation_DeleteMessagesForMe: String { return self._s[2099]! } - public var Notification_PassportValuePersonalDetails: String { return self._s[2100]! } - public var Message_PinnedAnimationMessage: String { return self._s[2101]! } - public var Passport_FieldIdentityUploadHelp: String { return self._s[2102]! } - public var SocksProxySetup_ConnectAndSave: String { return self._s[2103]! } - public var SocksProxySetup_FailedToConnect: String { return self._s[2104]! } - public var Checkout_ErrorPrecheckoutFailed: String { return self._s[2105]! } - public var Camera_PhotoMode: String { return self._s[2107]! } + public var Channel_AdminLog_CanAddAdmins: String { return self._s[2092]! } + public var TwoStepAuth_SetupHint: String { return self._s[2093]! } + public var Conversation_StatusLeftGroup: String { return self._s[2094]! } + public var Settings_CopyUsername: String { return self._s[2095]! } + public var Passport_Identity_CountryPlaceholder: String { return self._s[2096]! } + public var ChatSettings_AutoDownloadDocuments: String { return self._s[2097]! } + public var MediaPicker_TapToUngroupDescription: String { return self._s[2098]! } + public var Conversation_ShareBotLocationConfirmation: String { return self._s[2099]! } + public var Conversation_DeleteMessagesForMe: String { return self._s[2100]! } + public var Notification_PassportValuePersonalDetails: String { return self._s[2101]! } + public var Message_PinnedAnimationMessage: String { return self._s[2102]! } + public var Passport_FieldIdentityUploadHelp: String { return self._s[2103]! } + public var SocksProxySetup_ConnectAndSave: String { return self._s[2104]! } + public var SocksProxySetup_FailedToConnect: String { return self._s[2105]! } + public var Checkout_ErrorPrecheckoutFailed: String { return self._s[2106]! } + public var Camera_PhotoMode: String { return self._s[2108]! } public func Time_MonthOfYear_m2(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2109]!, self._r[2109]!, [_0]) + return formatWithArgumentRanges(self._s[2110]!, self._r[2110]!, [_0]) } - public var Channel_About_Placeholder: String { return self._s[2110]! } - public var Map_Directions: String { return self._s[2112]! } - public var Channel_About_Title: String { return self._s[2113]! } + public var Channel_About_Placeholder: String { return self._s[2111]! } + public var Map_Directions: String { return self._s[2113]! } + public var Channel_About_Title: String { return self._s[2114]! } public func MESSAGE_PHOTO(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2114]!, self._r[2114]!, [_1]) + return formatWithArgumentRanges(self._s[2115]!, self._r[2115]!, [_1]) } - public var Calls_RatingTitle: String { return self._s[2115]! } - public var SharedMedia_EmptyText: String { return self._s[2116]! } - public var Channel_Stickers_Searching: String { return self._s[2117]! } - public var Passport_Address_AddUtilityBill: String { return self._s[2118]! } - public var Login_PadPhoneHelp: String { return self._s[2119]! } - public var StickerPacksSettings_ArchivedPacks: String { return self._s[2121]! } - public var Passport_Language_th: String { return self._s[2122]! } - public var Channel_ErrorAccessDenied: String { return self._s[2123]! } - public var Generic_ErrorMoreInfo: String { return self._s[2125]! } - public var Channel_AdminLog_TitleAllEvents: String { return self._s[2126]! } - public var Settings_Proxy: String { return self._s[2127]! } - public var Passport_Language_lt: String { return self._s[2128]! } - public var ChannelMembers_WhoCanAddMembersAllHelp: String { return self._s[2130]! } - public var Passport_Address_CountryPlaceholder: String { return self._s[2131]! } - public var ChangePhoneNumberCode_CodePlaceholder: String { return self._s[2132]! } - public var Camera_SquareMode: String { return self._s[2133]! } + public var Calls_RatingTitle: String { return self._s[2116]! } + public var SharedMedia_EmptyText: String { return self._s[2117]! } + public var Channel_Stickers_Searching: String { return self._s[2118]! } + public var Passport_Address_AddUtilityBill: String { return self._s[2119]! } + public var Login_PadPhoneHelp: String { return self._s[2120]! } + public var StickerPacksSettings_ArchivedPacks: String { return self._s[2122]! } + public var Passport_Language_th: String { return self._s[2123]! } + public var Channel_ErrorAccessDenied: String { return self._s[2124]! } + public var Generic_ErrorMoreInfo: String { return self._s[2126]! } + public var Channel_AdminLog_TitleAllEvents: String { return self._s[2127]! } + public var Settings_Proxy: String { return self._s[2128]! } + public var Passport_Language_lt: String { return self._s[2129]! } + public var ChannelMembers_WhoCanAddMembersAllHelp: String { return self._s[2131]! } + public var Passport_Address_CountryPlaceholder: String { return self._s[2132]! } + public var ChangePhoneNumberCode_CodePlaceholder: String { return self._s[2133]! } + public var Camera_SquareMode: String { return self._s[2134]! } public func Conversation_EncryptedPlaceholderTitleOutgoing(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2134]!, self._r[2134]!, [_0]) + return formatWithArgumentRanges(self._s[2135]!, self._r[2135]!, [_0]) } - public var NetworkUsageSettings_CallDataSection: String { return self._s[2135]! } - public var Login_PadPhoneHelpTitle: String { return self._s[2136]! } - public var Profile_CreateNewContact: String { return self._s[2137]! } - public var AccessDenied_VideoMessageMicrophone: String { return self._s[2138]! } - public var AutoDownloadSettings_VoiceMessagesTitle: String { return self._s[2139]! } - public var PhotoEditor_VignetteTool: String { return self._s[2140]! } - public var LastSeen_WithinAWeek: String { return self._s[2141]! } - public var Widget_NoUsers: String { return self._s[2142]! } - public var Passport_Identity_DocumentNumber: String { return self._s[2144]! } - public var Application_Update: String { return self._s[2145]! } - public var Calls_NewCall: String { return self._s[2146]! } + public var NetworkUsageSettings_CallDataSection: String { return self._s[2136]! } + public var Login_PadPhoneHelpTitle: String { return self._s[2137]! } + public var Profile_CreateNewContact: String { return self._s[2138]! } + public var AccessDenied_VideoMessageMicrophone: String { return self._s[2139]! } + public var AutoDownloadSettings_VoiceMessagesTitle: String { return self._s[2140]! } + public var PhotoEditor_VignetteTool: String { return self._s[2141]! } + public var LastSeen_WithinAWeek: String { return self._s[2142]! } + public var Widget_NoUsers: String { return self._s[2143]! } + public var Passport_Identity_DocumentNumber: String { return self._s[2145]! } + public var Application_Update: String { return self._s[2146]! } + public var Calls_NewCall: String { return self._s[2147]! } public func CHANNEL_MESSAGE_AUDIO(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2147]!, self._r[2147]!, [_1]) + return formatWithArgumentRanges(self._s[2148]!, self._r[2148]!, [_1]) } - public var DialogList_NoMessagesText: String { return self._s[2148]! } - public var MaskStickerSettings_Info: String { return self._s[2149]! } - public var ChatSettings_AutoDownloadTitle: String { return self._s[2150]! } - public var Passport_FieldAddressHelp: String { return self._s[2151]! } - public var Passport_Language_dz: String { return self._s[2152]! } - public var Conversation_FilePhotoOrVideo: String { return self._s[2153]! } - public var Channel_AdminLog_BanSendStickers: String { return self._s[2154]! } - public var Common_Next: String { return self._s[2155]! } - public var Stickers_RemoveFromFavorites: String { return self._s[2156]! } - public var Watch_Notification_Joined: String { return self._s[2157]! } + public var DialogList_NoMessagesText: String { return self._s[2149]! } + public var MaskStickerSettings_Info: String { return self._s[2150]! } + public var ChatSettings_AutoDownloadTitle: String { return self._s[2151]! } + public var Passport_FieldAddressHelp: String { return self._s[2152]! } + public var Passport_Language_dz: String { return self._s[2153]! } + public var Conversation_FilePhotoOrVideo: String { return self._s[2154]! } + public var Channel_AdminLog_BanSendStickers: String { return self._s[2155]! } + public var Common_Next: String { return self._s[2156]! } + public var Stickers_RemoveFromFavorites: String { return self._s[2157]! } + public var Watch_Notification_Joined: String { return self._s[2158]! } public func Channel_AdminLog_MessageRestrictedNewSetting(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2158]!, self._r[2158]!, [_0]) + return formatWithArgumentRanges(self._s[2159]!, self._r[2159]!, [_0]) } - public var Passport_DeleteAddress: String { return self._s[2159]! } - public var ContactInfo_PhoneLabelHome: String { return self._s[2160]! } - public var GroupInfo_DeleteAndExitConfirmation: String { return self._s[2161]! } - public var NotificationsSound_Tremolo: String { return self._s[2162]! } - public var TwoStepAuth_EmailInvalid: String { return self._s[2163]! } - public var Privacy_ContactsTitle: String { return self._s[2164]! } - public var Passport_Address_TypeBankStatement: String { return self._s[2166]! } + public var Passport_DeleteAddress: String { return self._s[2160]! } + public var ContactInfo_PhoneLabelHome: String { return self._s[2161]! } + public var GroupInfo_DeleteAndExitConfirmation: String { return self._s[2162]! } + public var NotificationsSound_Tremolo: String { return self._s[2163]! } + public var TwoStepAuth_EmailInvalid: String { return self._s[2164]! } + public var Privacy_ContactsTitle: String { return self._s[2165]! } + public var Passport_Address_TypeBankStatement: String { return self._s[2167]! } public func CHAT_MESSAGE_VIDEO(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2167]!, self._r[2167]!, [_1, _2]) + return formatWithArgumentRanges(self._s[2168]!, self._r[2168]!, [_1, _2]) } - public var Month_GenJune: String { return self._s[2168]! } - public var Map_LiveLocationFor15Minutes: String { return self._s[2169]! } + public var Month_GenJune: String { return self._s[2169]! } + public var Map_LiveLocationFor15Minutes: String { return self._s[2170]! } public func Login_EmailCodeSubject(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2170]!, self._r[2170]!, [_0]) + return formatWithArgumentRanges(self._s[2171]!, self._r[2171]!, [_0]) } public func CHAT_TITLE_EDITED(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2171]!, self._r[2171]!, [_1, _2]) + return formatWithArgumentRanges(self._s[2172]!, self._r[2172]!, [_1, _2]) } - public var ContactInfo_PhoneLabelHomeFax: String { return self._s[2172]! } + public var ContactInfo_PhoneLabelHomeFax: String { return self._s[2173]! } public func NetworkUsageSettings_WifiUsageSince(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2173]!, self._r[2173]!, [_0]) + return formatWithArgumentRanges(self._s[2174]!, self._r[2174]!, [_0]) } - public var Watch_LastSeen_Lately: String { return self._s[2174]! } - public var Watch_Compose_CurrentLocation: String { return self._s[2175]! } - public var DialogList_RecentTitlePeople: String { return self._s[2177]! } - public var GroupInfo_Notifications: String { return self._s[2178]! } - public var Call_ReportPlaceholder: String { return self._s[2179]! } + public var Watch_LastSeen_Lately: String { return self._s[2175]! } + public var Watch_Compose_CurrentLocation: String { return self._s[2176]! } + public var DialogList_RecentTitlePeople: String { return self._s[2178]! } + public var GroupInfo_Notifications: String { return self._s[2179]! } + public var Call_ReportPlaceholder: String { return self._s[2180]! } public func AuthSessions_Message(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2180]!, self._r[2180]!, [_0]) + return formatWithArgumentRanges(self._s[2181]!, self._r[2181]!, [_0]) } public func MESSAGE_DOC(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2181]!, self._r[2181]!, [_1]) + return formatWithArgumentRanges(self._s[2182]!, self._r[2182]!, [_1]) } - public var Group_Username_CreatePrivateLinkHelp: String { return self._s[2182]! } - public var Notifications_GroupNotificationsSound: String { return self._s[2183]! } - public var AuthSessions_EmptyTitle: String { return self._s[2184]! } - public var Privacy_GroupsAndChannels_AlwaysAllow_Title: String { return self._s[2186]! } - public var Passport_Language_he: String { return self._s[2187]! } + public var Group_Username_CreatePrivateLinkHelp: String { return self._s[2183]! } + public var Notifications_GroupNotificationsSound: String { return self._s[2184]! } + public var AuthSessions_EmptyTitle: String { return self._s[2185]! } + public var Privacy_GroupsAndChannels_AlwaysAllow_Title: String { return self._s[2187]! } + public var Passport_Language_he: String { return self._s[2188]! } public func MediaPicker_Nof(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2188]!, self._r[2188]!, [_0]) + return formatWithArgumentRanges(self._s[2189]!, self._r[2189]!, [_0]) } - public var Common_Create: String { return self._s[2189]! } - public var Contacts_TopSection: String { return self._s[2190]! } + public var Common_Create: String { return self._s[2190]! } + public var Contacts_TopSection: String { return self._s[2191]! } public func Map_DirectionsDriveEta(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2191]!, self._r[2191]!, [_0]) + return formatWithArgumentRanges(self._s[2192]!, self._r[2192]!, [_0]) } - public var PrivacyPolicy_DeclineMessage: String { return self._s[2192]! } - public var Your_cards_number_is_invalid: String { return self._s[2193]! } + public var PrivacyPolicy_DeclineMessage: String { return self._s[2193]! } + public var Your_cards_number_is_invalid: String { return self._s[2194]! } public func MESSAGE_INVOICE(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2194]!, self._r[2194]!, [_1, _2]) + return formatWithArgumentRanges(self._s[2195]!, self._r[2195]!, [_1, _2]) } - public var Localization_LanguageCustom: String { return self._s[2195]! } + public var Localization_LanguageCustom: String { return self._s[2196]! } public func Channel_AdminLog_MessageRemovedChannelUsername(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2196]!, self._r[2196]!, [_0]) + return formatWithArgumentRanges(self._s[2197]!, self._r[2197]!, [_0]) } - public var Group_MessagePhotoRemoved: String { return self._s[2197]! } - public var Appearance_Animations: String { return self._s[2198]! } - public var UserInfo_AddToExisting: String { return self._s[2199]! } - public var NotificationsSound_Aurora: String { return self._s[2200]! } + public var Group_MessagePhotoRemoved: String { return self._s[2198]! } + public var Appearance_Animations: String { return self._s[2199]! } + public var UserInfo_AddToExisting: String { return self._s[2200]! } + public var NotificationsSound_Aurora: String { return self._s[2201]! } public func LastSeen_AtDate(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2203]!, self._r[2203]!, [_0]) + return formatWithArgumentRanges(self._s[2204]!, self._r[2204]!, [_0]) } - public var Conversation_MessageDialogRetry: String { return self._s[2204]! } - public var Watch_ChatList_NoConversationsTitle: String { return self._s[2205]! } - public var Passport_Language_my: String { return self._s[2206]! } - public var Stickers_GroupStickers: String { return self._s[2208]! } - public var BlockedUsers_Title: String { return self._s[2210]! } + public var Conversation_MessageDialogRetry: String { return self._s[2205]! } + public var Watch_ChatList_NoConversationsTitle: String { return self._s[2206]! } + public var Passport_Language_my: String { return self._s[2207]! } + public var Stickers_GroupStickers: String { return self._s[2209]! } + public var BlockedUsers_Title: String { return self._s[2211]! } public func LiveLocationUpdated_TodayAt(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2211]!, self._r[2211]!, [_0]) + return formatWithArgumentRanges(self._s[2212]!, self._r[2212]!, [_0]) } - public var ContactInfo_PhoneLabelWork: String { return self._s[2212]! } - public var ChatSettings_ConnectionType_UseSocks5: String { return self._s[2213]! } - public var Passport_FieldAddressTranslationHelp: String { return self._s[2214]! } - public var Cache_ClearNone: String { return self._s[2215]! } - public var SecretTimer_VideoDescription: String { return self._s[2217]! } - public var Login_InvalidCodeError: String { return self._s[2218]! } - public var Channel_BanList_BlockedTitle: String { return self._s[2220]! } - public var Passport_PasswordHelp: String { return self._s[2221]! } - public var NetworkUsageSettings_Cellular: String { return self._s[2222]! } - public var Watch_Location_Access: String { return self._s[2223]! } - public var PrivacySettings_DeleteAccountIfAwayFor: String { return self._s[2225]! } - public var Channel_AdminLog_EmptyFilterText: String { return self._s[2226]! } - public var Channel_AdminLog_EmptyText: String { return self._s[2227]! } - public var PrivacySettings_DeleteAccountTitle: String { return self._s[2228]! } - public var Passport_Language_ms: String { return self._s[2229]! } - public var PrivacyLastSeenSettings_CustomShareSettings_Delete: String { return self._s[2230]! } + public var ContactInfo_PhoneLabelWork: String { return self._s[2213]! } + public var ChatSettings_ConnectionType_UseSocks5: String { return self._s[2214]! } + public var Passport_FieldAddressTranslationHelp: String { return self._s[2215]! } + public var Cache_ClearNone: String { return self._s[2216]! } + public var SecretTimer_VideoDescription: String { return self._s[2218]! } + public var Login_InvalidCodeError: String { return self._s[2219]! } + public var Channel_BanList_BlockedTitle: String { return self._s[2221]! } + public var Passport_PasswordHelp: String { return self._s[2222]! } + public var NetworkUsageSettings_Cellular: String { return self._s[2223]! } + public var Watch_Location_Access: String { return self._s[2224]! } + public var PrivacySettings_DeleteAccountIfAwayFor: String { return self._s[2226]! } + public var Channel_AdminLog_EmptyFilterText: String { return self._s[2227]! } + public var Channel_AdminLog_EmptyText: String { return self._s[2228]! } + public var PrivacySettings_DeleteAccountTitle: String { return self._s[2229]! } + public var Passport_Language_ms: String { return self._s[2230]! } + public var PrivacyLastSeenSettings_CustomShareSettings_Delete: String { return self._s[2231]! } public func ENCRYPTED_MESSAGE(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2231]!, self._r[2231]!, [_1]) + return formatWithArgumentRanges(self._s[2232]!, self._r[2232]!, [_1]) } - public var Watch_LastSeen_WithinAMonth: String { return self._s[2232]! } - public var PrivacyLastSeenSettings_CustomHelp: String { return self._s[2233]! } - public var TwoStepAuth_EnterPasswordHelp: String { return self._s[2234]! } - public var Bot_Stop: String { return self._s[2235]! } - public var Privacy_GroupsAndChannels_AlwaysAllow_Placeholder: String { return self._s[2236]! } - public var UserInfo_BotSettings: String { return self._s[2237]! } - public var Your_cards_expiration_month_is_invalid: String { return self._s[2238]! } - public var Passport_FieldIdentity: String { return self._s[2239]! } - public var PrivacyLastSeenSettings_EmpryUsersPlaceholder: String { return self._s[2240]! } - public var Passport_Identity_EditInternalPassport: String { return self._s[2241]! } + public var Watch_LastSeen_WithinAMonth: String { return self._s[2233]! } + public var PrivacyLastSeenSettings_CustomHelp: String { return self._s[2234]! } + public var TwoStepAuth_EnterPasswordHelp: String { return self._s[2235]! } + public var Bot_Stop: String { return self._s[2236]! } + public var Privacy_GroupsAndChannels_AlwaysAllow_Placeholder: String { return self._s[2237]! } + public var UserInfo_BotSettings: String { return self._s[2238]! } + public var Your_cards_expiration_month_is_invalid: String { return self._s[2239]! } + public var Passport_FieldIdentity: String { return self._s[2240]! } + public var PrivacyLastSeenSettings_EmpryUsersPlaceholder: String { return self._s[2241]! } + public var Passport_Identity_EditInternalPassport: String { return self._s[2242]! } public func CHANNEL_MESSAGE_ROUND(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2243]!, self._r[2243]!, [_1]) + return formatWithArgumentRanges(self._s[2244]!, self._r[2244]!, [_1]) } - public var Passport_Identity_LatinNameHelp: String { return self._s[2244]! } - public var SocksProxySetup_Port: String { return self._s[2245]! } - public var Message_VideoMessage: String { return self._s[2247]! } - public var Conversation_ContextMenuStickerPackInfo: String { return self._s[2248]! } - public var Login_ResetAccountProtected_LimitExceeded: String { return self._s[2249]! } + public var Passport_Identity_LatinNameHelp: String { return self._s[2245]! } + public var SocksProxySetup_Port: String { return self._s[2246]! } + public var Message_VideoMessage: String { return self._s[2248]! } + public var Conversation_ContextMenuStickerPackInfo: String { return self._s[2249]! } + public var Login_ResetAccountProtected_LimitExceeded: String { return self._s[2250]! } public func CHAT_DELETE_MEMBER(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2250]!, self._r[2250]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[2251]!, self._r[2251]!, [_1, _2, _3]) } - public var Conversation_DiscardVoiceMessageAction: String { return self._s[2251]! } - public var Camera_Title: String { return self._s[2252]! } - public var Passport_Identity_IssueDate: String { return self._s[2253]! } - public var PhotoEditor_CurvesBlue: String { return self._s[2255]! } - public var Message_PinnedVideoMessage: String { return self._s[2256]! } + public var Conversation_DiscardVoiceMessageAction: String { return self._s[2252]! } + public var Camera_Title: String { return self._s[2253]! } + public var Passport_Identity_IssueDate: String { return self._s[2254]! } + public var PhotoEditor_CurvesBlue: String { return self._s[2256]! } + public var Message_PinnedVideoMessage: String { return self._s[2257]! } public func Login_EmailPhoneSubject(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2257]!, self._r[2257]!, [_0]) + return formatWithArgumentRanges(self._s[2258]!, self._r[2258]!, [_0]) } - public var Passport_Phone_UseTelegramNumberHelp: String { return self._s[2259]! } - public var Group_EditAdmin_PermissionChangeInfo: String { return self._s[2260]! } - public var TwoStepAuth_Email: String { return self._s[2261]! } - public var Stickers_SuggestNone: String { return self._s[2262]! } - public var Map_SendMyCurrentLocation: String { return self._s[2264]! } + public var Passport_Phone_UseTelegramNumberHelp: String { return self._s[2260]! } + public var Group_EditAdmin_PermissionChangeInfo: String { return self._s[2261]! } + public var TwoStepAuth_Email: String { return self._s[2262]! } + public var Stickers_SuggestNone: String { return self._s[2263]! } + public var Map_SendMyCurrentLocation: String { return self._s[2265]! } public func MESSAGE_ROUND(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2265]!, self._r[2265]!, [_1]) + return formatWithArgumentRanges(self._s[2266]!, self._r[2266]!, [_1]) } - public var Passport_Identity_IssueDatePlaceholder: String { return self._s[2266]! } - public var Watch_Message_Invoice: String { return self._s[2267]! } - public var Map_Unknown: String { return self._s[2269]! } - public var Wallpaper_Set: String { return self._s[2271]! } - public var AccessDenied_Title: String { return self._s[2272]! } - public var SharedMedia_CategoryLinks: String { return self._s[2273]! } - public var Localization_LanguageOther: String { return self._s[2274]! } + public var Passport_Identity_IssueDatePlaceholder: String { return self._s[2267]! } + public var Watch_Message_Invoice: String { return self._s[2268]! } + public var Map_Unknown: String { return self._s[2270]! } + public var Wallpaper_Set: String { return self._s[2272]! } + public var AccessDenied_Title: String { return self._s[2273]! } + public var SharedMedia_CategoryLinks: String { return self._s[2274]! } + public var Localization_LanguageOther: String { return self._s[2275]! } public func CHAT_MESSAGES(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2275]!, self._r[2275]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[2276]!, self._r[2276]!, [_1, _2, _3]) } - public var SaveIncomingPhotosSettings_Title: String { return self._s[2276]! } - public var Passport_Identity_TypeDriversLicense: String { return self._s[2277]! } - public var FastTwoStepSetup_HintHelp: String { return self._s[2278]! } - public var Notifications_ExceptionsDefaultSound: String { return self._s[2279]! } - public var TwoStepAuth_EmailSkipAlert: String { return self._s[2280]! } - public var ChatSettings_Stickers: String { return self._s[2281]! } - public var Camera_FlashOff: String { return self._s[2282]! } - public var TwoStepAuth_Title: String { return self._s[2284]! } - public var Passport_Identity_Translation: String { return self._s[2285]! } - public var Checkout_ErrorProviderAccountTimeout: String { return self._s[2286]! } - public var TwoStepAuth_SetupPasswordEnterPasswordChange: String { return self._s[2287]! } - public var WebSearch_Images: String { return self._s[2288]! } - public var Conversation_typing: String { return self._s[2289]! } - public var Common_Back: String { return self._s[2290]! } - public var PrivacySettings_DataSettingsHelp: String { return self._s[2292]! } - public var Passport_Language_es: String { return self._s[2293]! } - public var Common_Search: String { return self._s[2294]! } + public var SaveIncomingPhotosSettings_Title: String { return self._s[2277]! } + public var Passport_Identity_TypeDriversLicense: String { return self._s[2278]! } + public var FastTwoStepSetup_HintHelp: String { return self._s[2279]! } + public var Notifications_ExceptionsDefaultSound: String { return self._s[2280]! } + public var TwoStepAuth_EmailSkipAlert: String { return self._s[2281]! } + public var ChatSettings_Stickers: String { return self._s[2282]! } + public var Camera_FlashOff: String { return self._s[2283]! } + public var TwoStepAuth_Title: String { return self._s[2285]! } + public var Passport_Identity_Translation: String { return self._s[2286]! } + public var Checkout_ErrorProviderAccountTimeout: String { return self._s[2287]! } + public var TwoStepAuth_SetupPasswordEnterPasswordChange: String { return self._s[2288]! } + public var WebSearch_Images: String { return self._s[2289]! } + public var Conversation_typing: String { return self._s[2290]! } + public var Common_Back: String { return self._s[2291]! } + public var PrivacySettings_DataSettingsHelp: String { return self._s[2293]! } + public var Passport_Language_es: String { return self._s[2294]! } + public var Common_Search: String { return self._s[2295]! } public func CancelResetAccount_Success(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2295]!, self._r[2295]!, [_0]) + return formatWithArgumentRanges(self._s[2296]!, self._r[2296]!, [_0]) } - public var Common_No: String { return self._s[2296]! } - public var Login_EmailNotConfiguredError: String { return self._s[2297]! } - public var Watch_Suggestion_OK: String { return self._s[2298]! } - public var Profile_AddToExisting: String { return self._s[2299]! } + public var Common_No: String { return self._s[2297]! } + public var Login_EmailNotConfiguredError: String { return self._s[2298]! } + public var Watch_Suggestion_OK: String { return self._s[2299]! } + public var Profile_AddToExisting: String { return self._s[2300]! } public func Passport_Identity_NativeNameTitle(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2301]!, self._r[2301]!, [_0]) + return formatWithArgumentRanges(self._s[2302]!, self._r[2302]!, [_0]) } public func PINNED_NOTEXT(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2302]!, self._r[2302]!, [_1]) + return formatWithArgumentRanges(self._s[2303]!, self._r[2303]!, [_1]) } public func Login_EmailCodeBody(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2303]!, self._r[2303]!, [_0]) + return formatWithArgumentRanges(self._s[2304]!, self._r[2304]!, [_0]) } - public var NotificationsSound_Keys: String { return self._s[2304]! } - public var Passport_Phone_Title: String { return self._s[2306]! } - public var Profile_About: String { return self._s[2307]! } + public var NotificationsSound_Keys: String { return self._s[2305]! } + public var Passport_Phone_Title: String { return self._s[2307]! } + public var Profile_About: String { return self._s[2308]! } public func EncryptionKey_Description(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2308]!, self._r[2308]!, [_1, _2]) + return formatWithArgumentRanges(self._s[2309]!, self._r[2309]!, [_1, _2]) } - public var Conversation_UnreadMessages: String { return self._s[2309]! } + public var Conversation_UnreadMessages: String { return self._s[2310]! } public func DialogList_LiveLocationSharingTo(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2310]!, self._r[2310]!, [_0]) + return formatWithArgumentRanges(self._s[2311]!, self._r[2311]!, [_0]) } - public var Tour_Title3: String { return self._s[2311]! } - public var Passport_Identity_FrontSide: String { return self._s[2312]! } - public var PrivacyLastSeenSettings_GroupsAndChannelsHelp: String { return self._s[2313]! } - public var Watch_Contacts_NoResults: String { return self._s[2314]! } - public var Passport_Language_id: String { return self._s[2315]! } - public var Passport_Identity_TypeIdentityCardUploadScan: String { return self._s[2316]! } - public var Watch_UserInfo_MuteTitle: String { return self._s[2317]! } + public var Tour_Title3: String { return self._s[2312]! } + public var Passport_Identity_FrontSide: String { return self._s[2313]! } + public var PrivacyLastSeenSettings_GroupsAndChannelsHelp: String { return self._s[2314]! } + public var Watch_Contacts_NoResults: String { return self._s[2315]! } + public var Passport_Language_id: String { return self._s[2316]! } + public var Passport_Identity_TypeIdentityCardUploadScan: String { return self._s[2317]! } + public var Watch_UserInfo_MuteTitle: String { return self._s[2318]! } public func Privacy_GroupsAndChannels_InviteToGroupError(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2320]!, self._r[2320]!, [_0, _1]) + return formatWithArgumentRanges(self._s[2321]!, self._r[2321]!, [_0, _1]) } public func Message_PinnedTextMessage(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2322]!, self._r[2322]!, [_0]) + return formatWithArgumentRanges(self._s[2323]!, self._r[2323]!, [_0]) } public func Watch_Time_ShortWeekdayAt(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2323]!, self._r[2323]!, [_1, _2]) + return formatWithArgumentRanges(self._s[2324]!, self._r[2324]!, [_1, _2]) } - public var Conversation_EmptyGifPanelPlaceholder: String { return self._s[2324]! } - public var DialogList_Typing: String { return self._s[2325]! } - public var Notification_CallBack: String { return self._s[2326]! } - public var Passport_Language_ru: String { return self._s[2327]! } - public var Map_LocatingError: String { return self._s[2328]! } - public var InfoPlist_NSFaceIDUsageDescription: String { return self._s[2330]! } - public var MediaPicker_Send: String { return self._s[2331]! } - public var ChannelIntro_Title: String { return self._s[2332]! } - public var AccessDenied_LocationAlwaysDenied: String { return self._s[2334]! } + public var Conversation_EmptyGifPanelPlaceholder: String { return self._s[2325]! } + public var DialogList_Typing: String { return self._s[2326]! } + public var Notification_CallBack: String { return self._s[2327]! } + public var Passport_Language_ru: String { return self._s[2328]! } + public var Map_LocatingError: String { return self._s[2329]! } + public var InfoPlist_NSFaceIDUsageDescription: String { return self._s[2331]! } + public var MediaPicker_Send: String { return self._s[2332]! } + public var ChannelIntro_Title: String { return self._s[2333]! } + public var AccessDenied_LocationAlwaysDenied: String { return self._s[2335]! } public func PINNED_GIF(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2335]!, self._r[2335]!, [_1]) + return formatWithArgumentRanges(self._s[2336]!, self._r[2336]!, [_1]) } public func InviteText_SingleContact(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2336]!, self._r[2336]!, [_0]) + return formatWithArgumentRanges(self._s[2337]!, self._r[2337]!, [_0]) } - public var Passport_Address_TypePassportRegistration: String { return self._s[2337]! } - public var Channel_EditAdmin_CannotEdit: String { return self._s[2340]! } - public var LoginPassword_PasswordHelp: String { return self._s[2342]! } - public var BlockedUsers_Unblock: String { return self._s[2343]! } - public var AutoDownloadSettings_Cellular: String { return self._s[2344]! } - public var Passport_Language_ro: String { return self._s[2345]! } + public var Passport_Address_TypePassportRegistration: String { return self._s[2338]! } + public var Channel_EditAdmin_CannotEdit: String { return self._s[2341]! } + public var LoginPassword_PasswordHelp: String { return self._s[2343]! } + public var BlockedUsers_Unblock: String { return self._s[2344]! } + public var AutoDownloadSettings_Cellular: String { return self._s[2345]! } + public var Passport_Language_ro: String { return self._s[2346]! } public func Time_MonthOfYear_m1(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2346]!, self._r[2346]!, [_0]) + return formatWithArgumentRanges(self._s[2347]!, self._r[2347]!, [_0]) } - public var Appearance_PreviewIncomingText: String { return self._s[2347]! } - public var Passport_Identity_DateOfBirthPlaceholder: String { return self._s[2348]! } - public var Notifications_GroupNotificationsAlert: String { return self._s[2349]! } - public var Paint_Masks: String { return self._s[2350]! } - public var Appearance_ThemeDayClassic: String { return self._s[2352]! } - public var StickerPack_ErrorNotFound: String { return self._s[2353]! } - public var Appearance_ThemeNight: String { return self._s[2354]! } - public var SecretTimer_ImageDescription: String { return self._s[2355]! } + public var Appearance_PreviewIncomingText: String { return self._s[2348]! } + public var Passport_Identity_DateOfBirthPlaceholder: String { return self._s[2349]! } + public var Notifications_GroupNotificationsAlert: String { return self._s[2350]! } + public var Paint_Masks: String { return self._s[2351]! } + public var Appearance_ThemeDayClassic: String { return self._s[2353]! } + public var StickerPack_ErrorNotFound: String { return self._s[2354]! } + public var Appearance_ThemeNight: String { return self._s[2355]! } + public var SecretTimer_ImageDescription: String { return self._s[2356]! } public func PINNED_CONTACT(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2356]!, self._r[2356]!, [_1]) + return formatWithArgumentRanges(self._s[2357]!, self._r[2357]!, [_1]) } public func FileSize_KB(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2357]!, self._r[2357]!, [_0]) + return formatWithArgumentRanges(self._s[2358]!, self._r[2358]!, [_0]) } - public var Map_LiveLocationTitle: String { return self._s[2358]! } - public var Watch_GroupInfo_Title: String { return self._s[2359]! } - public var Channel_AdminLog_EmptyTitle: String { return self._s[2360]! } - public var PhotoEditor_Set: String { return self._s[2362]! } - public var LiveLocation_MenuStopAll: String { return self._s[2363]! } - public var SocksProxySetup_AddProxy: String { return self._s[2364]! } + public var Map_LiveLocationTitle: String { return self._s[2359]! } + public var Watch_GroupInfo_Title: String { return self._s[2360]! } + public var Channel_AdminLog_EmptyTitle: String { return self._s[2361]! } + public var PhotoEditor_Set: String { return self._s[2363]! } + public var LiveLocation_MenuStopAll: String { return self._s[2364]! } + public var SocksProxySetup_AddProxy: String { return self._s[2365]! } public func Notification_Invited(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2365]!, self._r[2365]!, [_0, _1]) + return formatWithArgumentRanges(self._s[2366]!, self._r[2366]!, [_0, _1]) } - public var Watch_AuthRequired: String { return self._s[2366]! } - public var Conversation_EncryptedDescription1: String { return self._s[2367]! } - public var AppleWatch_ReplyPresets: String { return self._s[2368]! } - public var Channel_Members_AddAdminErrorNotAMember: String { return self._s[2369]! } - public var Conversation_EncryptedDescription2: String { return self._s[2370]! } - public var SocksProxySetup_HostnamePlaceholder: String { return self._s[2371]! } - public var NetworkUsageSettings_MediaVideoDataSection: String { return self._s[2372]! } - public var Paint_Edit: String { return self._s[2373]! } - public var Passport_Language_nl: String { return self._s[2374]! } - public var LastSeen_Offline: String { return self._s[2375]! } - public var Login_CodeFloodError: String { return self._s[2376]! } - public var Conversation_EncryptedDescription3: String { return self._s[2377]! } - public var Notifications_Badge_IncludePublicGroups: String { return self._s[2379]! } - public var Conversation_EncryptedDescription4: String { return self._s[2380]! } - public var AppleWatch_Title: String { return self._s[2382]! } - public var Contacts_AccessDeniedError: String { return self._s[2383]! } - public var Conversation_StatusTyping: String { return self._s[2384]! } - public var Share_Title: String { return self._s[2385]! } - public var TwoStepAuth_ConfirmationTitle: String { return self._s[2386]! } - public var Passport_Identity_FilesTitle: String { return self._s[2387]! } - public var ChatSettings_Title: String { return self._s[2388]! } - public var AuthSessions_CurrentSession: String { return self._s[2389]! } - public var Watch_Microphone_Access: String { return self._s[2390]! } + public var Watch_AuthRequired: String { return self._s[2367]! } + public var Conversation_EncryptedDescription1: String { return self._s[2368]! } + public var AppleWatch_ReplyPresets: String { return self._s[2369]! } + public var Channel_Members_AddAdminErrorNotAMember: String { return self._s[2370]! } + public var Conversation_EncryptedDescription2: String { return self._s[2371]! } + public var SocksProxySetup_HostnamePlaceholder: String { return self._s[2372]! } + public var NetworkUsageSettings_MediaVideoDataSection: String { return self._s[2373]! } + public var Paint_Edit: String { return self._s[2374]! } + public var Passport_Language_nl: String { return self._s[2375]! } + public var LastSeen_Offline: String { return self._s[2376]! } + public var Login_CodeFloodError: String { return self._s[2377]! } + public var Conversation_EncryptedDescription3: String { return self._s[2378]! } + public var Notifications_Badge_IncludePublicGroups: String { return self._s[2380]! } + public var Conversation_EncryptedDescription4: String { return self._s[2381]! } + public var AppleWatch_Title: String { return self._s[2383]! } + public var Contacts_AccessDeniedError: String { return self._s[2384]! } + public var Conversation_StatusTyping: String { return self._s[2385]! } + public var Share_Title: String { return self._s[2386]! } + public var TwoStepAuth_ConfirmationTitle: String { return self._s[2387]! } + public var Passport_Identity_FilesTitle: String { return self._s[2388]! } + public var ChatSettings_Title: String { return self._s[2389]! } + public var AuthSessions_CurrentSession: String { return self._s[2390]! } + public var Watch_Microphone_Access: String { return self._s[2391]! } public func Notification_RenamedChat(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2391]!, self._r[2391]!, [_0]) + return formatWithArgumentRanges(self._s[2392]!, self._r[2392]!, [_0]) } - public var Conversation_LiveLocation: String { return self._s[2392]! } - public var Watch_Conversation_GroupInfo: String { return self._s[2393]! } - public var Passport_Language_fr: String { return self._s[2395]! } - public var UserInfo_Title: String { return self._s[2396]! } - public var Passport_Identity_DoesNotExpire: String { return self._s[2397]! } - public var Map_LiveLocationGroupDescription: String { return self._s[2398]! } - public var Login_InfoHelp: String { return self._s[2399]! } - public var ShareMenu_ShareTo: String { return self._s[2401]! } - public var Message_PinnedGame: String { return self._s[2405]! } - public var Channel_AdminLog_CanSendMessages: String { return self._s[2406]! } + public var Conversation_LiveLocation: String { return self._s[2393]! } + public var Watch_Conversation_GroupInfo: String { return self._s[2394]! } + public var Passport_Language_fr: String { return self._s[2396]! } + public var UserInfo_Title: String { return self._s[2397]! } + public var Passport_Identity_DoesNotExpire: String { return self._s[2398]! } + public var Map_LiveLocationGroupDescription: String { return self._s[2399]! } + public var Login_InfoHelp: String { return self._s[2400]! } + public var ShareMenu_ShareTo: String { return self._s[2402]! } + public var Message_PinnedGame: String { return self._s[2406]! } + public var Channel_AdminLog_CanSendMessages: String { return self._s[2407]! } public func AutoNightTheme_LocationHelp(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2407]!, self._r[2407]!, [_0, _1]) + return formatWithArgumentRanges(self._s[2408]!, self._r[2408]!, [_0, _1]) } - public var Notification_RenamedGroup: String { return self._s[2408]! } + public var Notification_RenamedGroup: String { return self._s[2409]! } public func Call_PrivacyErrorMessage(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2409]!, self._r[2409]!, [_0]) + return formatWithArgumentRanges(self._s[2410]!, self._r[2410]!, [_0]) } - public var Passport_Address_Street: String { return self._s[2410]! } - public var Weekday_Thursday: String { return self._s[2411]! } - public var FastTwoStepSetup_HintPlaceholder: String { return self._s[2412]! } - public var PrivacySettings_DataSettings: String { return self._s[2413]! } - public var ChangePhoneNumberNumber_Title: String { return self._s[2414]! } - public var NotificationsSound_Bell: String { return self._s[2415]! } - public var Notifications_Badge_IncludeMutedChats: String { return self._s[2417]! } - public var TwoStepAuth_EnterPasswordInvalid: String { return self._s[2419]! } - public var DialogList_SearchSectionMessages: String { return self._s[2420]! } - public var Media_ShareThisVideo: String { return self._s[2421]! } - public var Call_ReportIncludeLogDescription: String { return self._s[2422]! } - public var Preview_DeleteGif: String { return self._s[2423]! } - public var Passport_Address_OneOfTypeTemporaryRegistration: String { return self._s[2424]! } - public var Weekday_Saturday: String { return self._s[2425]! } - public var UserInfo_DeleteContact: String { return self._s[2426]! } - public var Notifications_ResetAllNotifications: String { return self._s[2427]! } - public var SocksProxySetup_SaveProxy: String { return self._s[2428]! } - public var Passport_Identity_Country: String { return self._s[2429]! } - public var Notification_MessageLifetimeRemovedOutgoing: String { return self._s[2430]! } - public var Login_ContinueWithLocalization: String { return self._s[2431]! } - public var GroupInfo_AddParticipant: String { return self._s[2432]! } - public var Watch_Location_Current: String { return self._s[2433]! } - public var Checkout_NewCard_SaveInfoHelp: String { return self._s[2434]! } + public var Passport_Address_Street: String { return self._s[2411]! } + public var Weekday_Thursday: String { return self._s[2412]! } + public var FastTwoStepSetup_HintPlaceholder: String { return self._s[2413]! } + public var PrivacySettings_DataSettings: String { return self._s[2414]! } + public var ChangePhoneNumberNumber_Title: String { return self._s[2415]! } + public var NotificationsSound_Bell: String { return self._s[2416]! } + public var Notifications_Badge_IncludeMutedChats: String { return self._s[2418]! } + public var TwoStepAuth_EnterPasswordInvalid: String { return self._s[2420]! } + public var DialogList_SearchSectionMessages: String { return self._s[2421]! } + public var Media_ShareThisVideo: String { return self._s[2422]! } + public var Call_ReportIncludeLogDescription: String { return self._s[2423]! } + public var Preview_DeleteGif: String { return self._s[2424]! } + public var Passport_Address_OneOfTypeTemporaryRegistration: String { return self._s[2425]! } + public var Weekday_Saturday: String { return self._s[2426]! } + public var UserInfo_DeleteContact: String { return self._s[2427]! } + public var Notifications_ResetAllNotifications: String { return self._s[2428]! } + public var SocksProxySetup_SaveProxy: String { return self._s[2429]! } + public var Passport_Identity_Country: String { return self._s[2430]! } + public var Notification_MessageLifetimeRemovedOutgoing: String { return self._s[2431]! } + public var Login_ContinueWithLocalization: String { return self._s[2432]! } + public var GroupInfo_AddParticipant: String { return self._s[2433]! } + public var Watch_Location_Current: String { return self._s[2434]! } + public var Checkout_NewCard_SaveInfoHelp: String { return self._s[2435]! } public func Settings_ApplyProxyAlertCredentials(_ _1: String, _ _2: String, _ _3: String, _ _4: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2437]!, self._r[2437]!, [_1, _2, _3, _4]) + return formatWithArgumentRanges(self._s[2438]!, self._r[2438]!, [_1, _2, _3, _4]) } - public var MediaPicker_CameraRoll: String { return self._s[2438]! } - public var Channel_AdminLog_CanPinMessages: String { return self._s[2439]! } - public var KeyCommand_NewMessage: String { return self._s[2440]! } + public var MediaPicker_CameraRoll: String { return self._s[2439]! } + public var Channel_AdminLog_CanPinMessages: String { return self._s[2440]! } + public var KeyCommand_NewMessage: String { return self._s[2441]! } public func ChannelInfo_AddParticipantConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2442]!, self._r[2442]!, [_0]) + return formatWithArgumentRanges(self._s[2443]!, self._r[2443]!, [_0]) } - public var NetworkUsageSettings_TotalSection: String { return self._s[2443]! } + public var NetworkUsageSettings_TotalSection: String { return self._s[2444]! } public func PINNED_AUDIO(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2444]!, self._r[2444]!, [_1]) + return formatWithArgumentRanges(self._s[2445]!, self._r[2445]!, [_1]) } - public var Privacy_GroupsAndChannels: String { return self._s[2445]! } + public var Privacy_GroupsAndChannels: String { return self._s[2446]! } public func Time_PreciseDate_m12(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2447]!, self._r[2447]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[2448]!, self._r[2448]!, [_1, _2, _3]) } - public var Conversation_DiscardVoiceMessageDescription: String { return self._s[2450]! } - public var Passport_Address_ScansHelp: String { return self._s[2451]! } + public var Conversation_DiscardVoiceMessageDescription: String { return self._s[2451]! } + public var Passport_Address_ScansHelp: String { return self._s[2452]! } public func Notification_ChangedGroupPhoto(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2452]!, self._r[2452]!, [_0]) + return formatWithArgumentRanges(self._s[2453]!, self._r[2453]!, [_0]) } - public var TwoStepAuth_RemovePassword: String { return self._s[2454]! } - public var Privacy_GroupsAndChannels_CustomHelp: String { return self._s[2456]! } - public var Passport_Identity_Gender: String { return self._s[2457]! } - public var UserInfo_NotificationsDisable: String { return self._s[2458]! } - public var Watch_UserInfo_Service: String { return self._s[2459]! } - public var Privacy_Calls_CustomHelp: String { return self._s[2461]! } - public var ChangePhoneNumberCode_Code: String { return self._s[2462]! } - public var UserInfo_Invite: String { return self._s[2463]! } - public var CheckoutInfo_ErrorStateInvalid: String { return self._s[2464]! } - public var DialogList_ClearHistoryConfirmation: String { return self._s[2466]! } - public var CheckoutInfo_ErrorEmailInvalid: String { return self._s[2468]! } - public var Month_GenNovember: String { return self._s[2469]! } - public var UserInfo_NotificationsEnable: String { return self._s[2470]! } + public var TwoStepAuth_RemovePassword: String { return self._s[2455]! } + public var Privacy_GroupsAndChannels_CustomHelp: String { return self._s[2457]! } + public var Passport_Identity_Gender: String { return self._s[2458]! } + public var UserInfo_NotificationsDisable: String { return self._s[2459]! } + public var Watch_UserInfo_Service: String { return self._s[2460]! } + public var Privacy_Calls_CustomHelp: String { return self._s[2462]! } + public var ChangePhoneNumberCode_Code: String { return self._s[2463]! } + public var UserInfo_Invite: String { return self._s[2464]! } + public var CheckoutInfo_ErrorStateInvalid: String { return self._s[2465]! } + public var DialogList_ClearHistoryConfirmation: String { return self._s[2467]! } + public var CheckoutInfo_ErrorEmailInvalid: String { return self._s[2469]! } + public var Month_GenNovember: String { return self._s[2470]! } + public var UserInfo_NotificationsEnable: String { return self._s[2471]! } public func Target_InviteToGroupConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2472]!, self._r[2472]!, [_0]) + return formatWithArgumentRanges(self._s[2473]!, self._r[2473]!, [_0]) } - public var Map_Map: String { return self._s[2473]! } - public var Map_OpenInMaps: String { return self._s[2474]! } - public var Common_OK: String { return self._s[2475]! } - public var TwoStepAuth_SetupHintTitle: String { return self._s[2476]! } - public var GroupInfo_LeftStatus: String { return self._s[2477]! } - public var Cache_ClearProgress: String { return self._s[2478]! } - public var Login_InvalidPhoneError: String { return self._s[2479]! } - public var Passport_Authorize: String { return self._s[2480]! } - public var Cache_ClearEmpty: String { return self._s[2481]! } - public var Map_Search: String { return self._s[2482]! } - public var Passport_Identity_Translations: String { return self._s[2483]! } - public var ChannelMembers_GroupAdminsTitle: String { return self._s[2485]! } + public var Map_Map: String { return self._s[2474]! } + public var Map_OpenInMaps: String { return self._s[2475]! } + public var Common_OK: String { return self._s[2476]! } + public var TwoStepAuth_SetupHintTitle: String { return self._s[2477]! } + public var GroupInfo_LeftStatus: String { return self._s[2478]! } + public var Cache_ClearProgress: String { return self._s[2479]! } + public var Login_InvalidPhoneError: String { return self._s[2480]! } + public var Passport_Authorize: String { return self._s[2481]! } + public var Cache_ClearEmpty: String { return self._s[2482]! } + public var Map_Search: String { return self._s[2483]! } + public var Passport_Identity_Translations: String { return self._s[2484]! } + public var ChannelMembers_GroupAdminsTitle: String { return self._s[2486]! } public func Channel_AdminLog_MessageRemovedGroupUsername(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2486]!, self._r[2486]!, [_0]) + return formatWithArgumentRanges(self._s[2487]!, self._r[2487]!, [_0]) } - public var ChatSettings_AutomaticPhotoDownload: String { return self._s[2487]! } - public var Group_ErrorAddTooMuchAdmins: String { return self._s[2489]! } - public var SocksProxySetup_Password: String { return self._s[2491]! } - public var Login_SelectCountry_Title: String { return self._s[2492]! } + public var ChatSettings_AutomaticPhotoDownload: String { return self._s[2488]! } + public var Group_ErrorAddTooMuchAdmins: String { return self._s[2490]! } + public var SocksProxySetup_Password: String { return self._s[2492]! } + public var Login_SelectCountry_Title: String { return self._s[2493]! } public func MESSAGE_PHOTOS(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2493]!, self._r[2493]!, [_1, _2]) + return formatWithArgumentRanges(self._s[2494]!, self._r[2494]!, [_1, _2]) } - public var Notifications_GroupNotificationsHelp: String { return self._s[2494]! } - public var PhotoEditor_CropAspectRatioSquare: String { return self._s[2495]! } - public var Notification_CallOutgoing: String { return self._s[2496]! } - public var UserInfo_NotificationsDefault: String { return self._s[2497]! } - public var Weekday_ShortMonday: String { return self._s[2498]! } - public var Checkout_Receipt_Title: String { return self._s[2499]! } - public var Channel_Edit_AboutItem: String { return self._s[2500]! } - public var Login_InfoLastNamePlaceholder: String { return self._s[2501]! } - public var Channel_Members_AddMembersHelp: String { return self._s[2503]! } + public var Notifications_GroupNotificationsHelp: String { return self._s[2495]! } + public var PhotoEditor_CropAspectRatioSquare: String { return self._s[2496]! } + public var Notification_CallOutgoing: String { return self._s[2497]! } + public var UserInfo_NotificationsDefault: String { return self._s[2498]! } + public var Weekday_ShortMonday: String { return self._s[2499]! } + public var Checkout_Receipt_Title: String { return self._s[2500]! } + public var Channel_Edit_AboutItem: String { return self._s[2501]! } + public var Login_InfoLastNamePlaceholder: String { return self._s[2502]! } + public var Channel_Members_AddMembersHelp: String { return self._s[2504]! } public func MESSAGE_VIDEO_SECRET(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2504]!, self._r[2504]!, [_1]) + return formatWithArgumentRanges(self._s[2505]!, self._r[2505]!, [_1]) } - public var Settings_CopyPhoneNumber: String { return self._s[2505]! } - public var ReportPeer_Report: String { return self._s[2506]! } - public var Channel_EditMessageErrorGeneric: String { return self._s[2507]! } - public var Passport_Identity_TranslationsHelp: String { return self._s[2508]! } - public var LoginPassword_FloodError: String { return self._s[2509]! } - public var TwoStepAuth_SetupPasswordTitle: String { return self._s[2511]! } - public var PhotoEditor_DiscardChanges: String { return self._s[2512]! } - public var Group_UpgradeNoticeText2: String { return self._s[2513]! } + public var Settings_CopyPhoneNumber: String { return self._s[2506]! } + public var ReportPeer_Report: String { return self._s[2507]! } + public var Channel_EditMessageErrorGeneric: String { return self._s[2508]! } + public var Passport_Identity_TranslationsHelp: String { return self._s[2509]! } + public var LoginPassword_FloodError: String { return self._s[2510]! } + public var TwoStepAuth_SetupPasswordTitle: String { return self._s[2512]! } + public var PhotoEditor_DiscardChanges: String { return self._s[2513]! } + public var Group_UpgradeNoticeText2: String { return self._s[2514]! } public func PINNED_ROUND(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2514]!, self._r[2514]!, [_1]) + return formatWithArgumentRanges(self._s[2515]!, self._r[2515]!, [_1]) } public func ChannelInfo_ChannelForbidden(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2515]!, self._r[2515]!, [_0]) + return formatWithArgumentRanges(self._s[2516]!, self._r[2516]!, [_0]) } - public var Conversation_ShareMyContactInfo: String { return self._s[2516]! } - public var SocksProxySetup_UsernamePlaceholder: String { return self._s[2517]! } + public var Conversation_ShareMyContactInfo: String { return self._s[2517]! } + public var SocksProxySetup_UsernamePlaceholder: String { return self._s[2518]! } public func CHANNEL_MESSAGE_GEO(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2518]!, self._r[2518]!, [_1]) + return formatWithArgumentRanges(self._s[2519]!, self._r[2519]!, [_1]) } - public var Contacts_PhoneNumber: String { return self._s[2519]! } - public var Group_Info_AdminLog: String { return self._s[2520]! } - public var Channel_AdminLogFilter_ChannelEventsInfo: String { return self._s[2521]! } - public var ChatSettings_AutoDownloadEnabled: String { return self._s[2522]! } - public var StickerPacksSettings_FeaturedPacks: String { return self._s[2523]! } - public var AuthSessions_LoggedIn: String { return self._s[2524]! } - public var Month_GenAugust: String { return self._s[2525]! } - public var Notification_CallCanceled: String { return self._s[2526]! } - public var Channel_Username_CreatePublicLinkHelp: String { return self._s[2527]! } - public var StickerPack_Send: String { return self._s[2528]! } - public var StickerSettings_MaskContextInfo: String { return self._s[2529]! } - public var Watch_Suggestion_HoldOn: String { return self._s[2530]! } + public var Contacts_PhoneNumber: String { return self._s[2520]! } + public var Group_Info_AdminLog: String { return self._s[2521]! } + public var Channel_AdminLogFilter_ChannelEventsInfo: String { return self._s[2522]! } + public var ChatSettings_AutoDownloadEnabled: String { return self._s[2523]! } + public var StickerPacksSettings_FeaturedPacks: String { return self._s[2524]! } + public var AuthSessions_LoggedIn: String { return self._s[2525]! } + public var Month_GenAugust: String { return self._s[2526]! } + public var Notification_CallCanceled: String { return self._s[2527]! } + public var Channel_Username_CreatePublicLinkHelp: String { return self._s[2528]! } + public var StickerPack_Send: String { return self._s[2529]! } + public var StickerSettings_MaskContextInfo: String { return self._s[2530]! } + public var Watch_Suggestion_HoldOn: String { return self._s[2531]! } public func PINNED_GEO(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2531]!, self._r[2531]!, [_1]) + return formatWithArgumentRanges(self._s[2532]!, self._r[2532]!, [_1]) } - public var PasscodeSettings_EncryptData: String { return self._s[2532]! } - public var Common_NotNow: String { return self._s[2533]! } - public var FastTwoStepSetup_PasswordConfirmationPlaceholder: String { return self._s[2535]! } - public var PasscodeSettings_Title: String { return self._s[2536]! } - public var StickerPack_BuiltinPackName: String { return self._s[2537]! } - public var Appearance_AccentColor: String { return self._s[2539]! } - public var Watch_Suggestion_BRB: String { return self._s[2540]! } + public var PasscodeSettings_EncryptData: String { return self._s[2533]! } + public var Common_NotNow: String { return self._s[2534]! } + public var FastTwoStepSetup_PasswordConfirmationPlaceholder: String { return self._s[2536]! } + public var PasscodeSettings_Title: String { return self._s[2537]! } + public var StickerPack_BuiltinPackName: String { return self._s[2538]! } + public var Appearance_AccentColor: String { return self._s[2540]! } + public var Watch_Suggestion_BRB: String { return self._s[2541]! } public func CHAT_MESSAGE_ROUND(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2541]!, self._r[2541]!, [_1, _2]) + return formatWithArgumentRanges(self._s[2542]!, self._r[2542]!, [_1, _2]) } - public var Notifications_MessageNotificationsAlert: String { return self._s[2542]! } - public var Username_InvalidCharacters: String { return self._s[2543]! } - public var GroupInfo_LabelAdmin: String { return self._s[2544]! } - public var GroupInfo_Sound: String { return self._s[2545]! } - public var Channel_EditAdmin_PermissionBanUsers: String { return self._s[2546]! } - public var InfoPlist_NSCameraUsageDescription: String { return self._s[2547]! } - public var Passport_Address_AddRentalAgreement: String { return self._s[2548]! } - public var Wallpaper_PhotoLibrary: String { return self._s[2549]! } - public var Settings_About: String { return self._s[2550]! } - public var Privacy_Calls_IntegrationHelp: String { return self._s[2551]! } - public var ContactInfo_Job: String { return self._s[2552]! } + public var Notifications_MessageNotificationsAlert: String { return self._s[2543]! } + public var Username_InvalidCharacters: String { return self._s[2544]! } + public var GroupInfo_LabelAdmin: String { return self._s[2545]! } + public var GroupInfo_Sound: String { return self._s[2546]! } + public var Channel_EditAdmin_PermissionBanUsers: String { return self._s[2547]! } + public var InfoPlist_NSCameraUsageDescription: String { return self._s[2548]! } + public var Passport_Address_AddRentalAgreement: String { return self._s[2549]! } + public var Wallpaper_PhotoLibrary: String { return self._s[2550]! } + public var Settings_About: String { return self._s[2551]! } + public var Privacy_Calls_IntegrationHelp: String { return self._s[2552]! } + public var ContactInfo_Job: String { return self._s[2553]! } public func CHAT_LEFT(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2553]!, self._r[2553]!, [_1, _2]) + return formatWithArgumentRanges(self._s[2554]!, self._r[2554]!, [_1, _2]) } - public var LoginPassword_ForgotPassword: String { return self._s[2554]! } - public var Passport_Address_AddTemporaryRegistration: String { return self._s[2556]! } + public var LoginPassword_ForgotPassword: String { return self._s[2555]! } + public var Passport_Address_AddTemporaryRegistration: String { return self._s[2557]! } public func Map_LiveLocationShortHour(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2557]!, self._r[2557]!, [_0]) + return formatWithArgumentRanges(self._s[2558]!, self._r[2558]!, [_0]) } - public var Appearance_Preview: String { return self._s[2558]! } + public var Appearance_Preview: String { return self._s[2559]! } public func DialogList_AwaitingEncryption(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2559]!, self._r[2559]!, [_0]) + return formatWithArgumentRanges(self._s[2560]!, self._r[2560]!, [_0]) } - public var Passport_Identity_TypePassport: String { return self._s[2560]! } - public var ChatSettings_Appearance: String { return self._s[2561]! } - public var Tour_Title1: String { return self._s[2562]! } - public var Conversation_EditingCaptionPanelTitle: String { return self._s[2564]! } + public var Passport_Identity_TypePassport: String { return self._s[2561]! } + public var ChatSettings_Appearance: String { return self._s[2562]! } + public var Tour_Title1: String { return self._s[2563]! } + public var Conversation_EditingCaptionPanelTitle: String { return self._s[2565]! } public func Notifications_ExceptionsChangeSound(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2565]!, self._r[2565]!, [_0]) + return formatWithArgumentRanges(self._s[2566]!, self._r[2566]!, [_0]) } - public var Conversation_LinkDialogCopy: String { return self._s[2566]! } + public var Conversation_LinkDialogCopy: String { return self._s[2567]! } public func Notification_PinnedLocationMessage(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2567]!, self._r[2567]!, [_0]) - } - public func Notification_PinnedPhotoMessage(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(self._s[2568]!, self._r[2568]!, [_0]) } + public func Notification_PinnedPhotoMessage(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2569]!, self._r[2569]!, [_0]) + } public func DownloadingStatus(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2569]!, self._r[2569]!, [_0, _1]) + return formatWithArgumentRanges(self._s[2570]!, self._r[2570]!, [_0, _1]) } - public var Calls_All: String { return self._s[2570]! } + public var Calls_All: String { return self._s[2571]! } public func Channel_MessageTitleUpdated(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2571]!, self._r[2571]!, [_0]) + return formatWithArgumentRanges(self._s[2572]!, self._r[2572]!, [_0]) } - public var Call_CallAgain: String { return self._s[2572]! } - public var Message_VideoExpired: String { return self._s[2573]! } - public var TwoStepAuth_RecoveryCodeHelp: String { return self._s[2574]! } + public var Call_CallAgain: String { return self._s[2573]! } + public var Message_VideoExpired: String { return self._s[2574]! } + public var TwoStepAuth_RecoveryCodeHelp: String { return self._s[2575]! } public func Channel_AdminLog_MessagePromotedNameUsername(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2575]!, self._r[2575]!, [_1, _2]) + return formatWithArgumentRanges(self._s[2576]!, self._r[2576]!, [_1, _2]) } - public var UserInfo_SendMessage: String { return self._s[2576]! } + public var UserInfo_SendMessage: String { return self._s[2577]! } public func Channel_Username_LinkHint(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2577]!, self._r[2577]!, [_0]) - } - public func AutoDownloadSettings_UpTo(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(self._s[2578]!, self._r[2578]!, [_0]) } - public var Settings_ViewPhoto: String { return self._s[2579]! } - public var Paint_RecentStickers: String { return self._s[2580]! } + public func AutoDownloadSettings_UpTo(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2579]!, self._r[2579]!, [_0]) + } + public var Settings_ViewPhoto: String { return self._s[2580]! } + public var Paint_RecentStickers: String { return self._s[2581]! } public func Passport_PrivacyPolicy(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2581]!, self._r[2581]!, [_1, _2]) + return formatWithArgumentRanges(self._s[2582]!, self._r[2582]!, [_1, _2]) } - public var Login_CallRequestState3: String { return self._s[2582]! } - public var Channel_Edit_LinkItem: String { return self._s[2583]! } - public var CallSettings_Title: String { return self._s[2585]! } - public var ChangePhoneNumberNumber_Help: String { return self._s[2586]! } - public var Passport_InfoTitle: String { return self._s[2587]! } - public var Watch_Suggestion_Thanks: String { return self._s[2588]! } - public var Channel_Moderator_Title: String { return self._s[2589]! } - public var Message_PinnedPhotoMessage: String { return self._s[2590]! } - public var Notification_SecretChatScreenshot: String { return self._s[2591]! } + public var Login_CallRequestState3: String { return self._s[2583]! } + public var Channel_Edit_LinkItem: String { return self._s[2584]! } + public var CallSettings_Title: String { return self._s[2586]! } + public var ChangePhoneNumberNumber_Help: String { return self._s[2587]! } + public var Passport_InfoTitle: String { return self._s[2588]! } + public var Watch_Suggestion_Thanks: String { return self._s[2589]! } + public var Channel_Moderator_Title: String { return self._s[2590]! } + public var Message_PinnedPhotoMessage: String { return self._s[2591]! } + public var Notification_SecretChatScreenshot: String { return self._s[2592]! } public func Conversation_DeleteMessagesFor(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2592]!, self._r[2592]!, [_0]) + return formatWithArgumentRanges(self._s[2593]!, self._r[2593]!, [_0]) } - public var Activity_UploadingDocument: String { return self._s[2593]! } - public var Watch_ChatList_NoConversationsText: String { return self._s[2594]! } - public var ReportPeer_AlertSuccess: String { return self._s[2595]! } - public var Tour_Text4: String { return self._s[2596]! } - public var Channel_Info_Description: String { return self._s[2597]! } - public var AccessDenied_LocationTracking: String { return self._s[2599]! } - public var Watch_Compose_Send: String { return self._s[2600]! } - public var SocksProxySetup_UseForCallsHelp: String { return self._s[2601]! } - public var Preview_CopyAddress: String { return self._s[2602]! } - public var Settings_BlockedUsers: String { return self._s[2603]! } - public var Month_ShortAugust: String { return self._s[2604]! } - public var Passport_Identity_MainPage: String { return self._s[2605]! } - public var Passport_FieldAddress: String { return self._s[2607]! } - public var Channel_AdminLogFilter_AdminsTitle: String { return self._s[2608]! } - public var Channel_EditAdmin_PermissionChangeInfo: String { return self._s[2609]! } - public var Notifications_ResetAllNotificationsHelp: String { return self._s[2610]! } - public var DialogList_EncryptionRejected: String { return self._s[2611]! } - public var Target_InviteToGroupErrorAlreadyInvited: String { return self._s[2613]! } - public var AccessDenied_CameraRestricted: String { return self._s[2614]! } - public var Watch_Message_ForwardedFrom: String { return self._s[2615]! } - public var CheckoutInfo_ShippingInfoCountryPlaceholder: String { return self._s[2617]! } - public var Channel_AboutItem: String { return self._s[2618]! } - public var PhotoEditor_CurvesGreen: String { return self._s[2619]! } - public var Month_GenJuly: String { return self._s[2620]! } - public var ContactInfo_URLLabelHomepage: String { return self._s[2621]! } - public var PrivacyPolicy_DeclineDeclineAndDelete: String { return self._s[2622]! } + public var Activity_UploadingDocument: String { return self._s[2594]! } + public var Watch_ChatList_NoConversationsText: String { return self._s[2595]! } + public var ReportPeer_AlertSuccess: String { return self._s[2596]! } + public var Tour_Text4: String { return self._s[2597]! } + public var Channel_Info_Description: String { return self._s[2598]! } + public var AccessDenied_LocationTracking: String { return self._s[2600]! } + public var Watch_Compose_Send: String { return self._s[2601]! } + public var SocksProxySetup_UseForCallsHelp: String { return self._s[2602]! } + public var Preview_CopyAddress: String { return self._s[2603]! } + public var Settings_BlockedUsers: String { return self._s[2604]! } + public var Month_ShortAugust: String { return self._s[2605]! } + public var Passport_Identity_MainPage: String { return self._s[2606]! } + public var Passport_FieldAddress: String { return self._s[2608]! } + public var Channel_AdminLogFilter_AdminsTitle: String { return self._s[2609]! } + public var Channel_EditAdmin_PermissionChangeInfo: String { return self._s[2610]! } + public var Notifications_ResetAllNotificationsHelp: String { return self._s[2611]! } + public var DialogList_EncryptionRejected: String { return self._s[2612]! } + public var Target_InviteToGroupErrorAlreadyInvited: String { return self._s[2614]! } + public var AccessDenied_CameraRestricted: String { return self._s[2615]! } + public var Watch_Message_ForwardedFrom: String { return self._s[2616]! } + public var CheckoutInfo_ShippingInfoCountryPlaceholder: String { return self._s[2618]! } + public var Channel_AboutItem: String { return self._s[2619]! } + public var PhotoEditor_CurvesGreen: String { return self._s[2620]! } + public var Month_GenJuly: String { return self._s[2621]! } + public var ContactInfo_URLLabelHomepage: String { return self._s[2622]! } + public var PrivacyPolicy_DeclineDeclineAndDelete: String { return self._s[2623]! } public func DialogList_SingleUploadingFileSuffix(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2624]!, self._r[2624]!, [_0]) + return formatWithArgumentRanges(self._s[2625]!, self._r[2625]!, [_0]) } - public var ChannelIntro_CreateChannel: String { return self._s[2626]! } - public var Channel_Management_AddModerator: String { return self._s[2627]! } - public var Common_ChoosePhoto: String { return self._s[2628]! } - public var Conversation_Pin: String { return self._s[2629]! } + public var ChannelIntro_CreateChannel: String { return self._s[2627]! } + public var Channel_Management_AddModerator: String { return self._s[2628]! } + public var Common_ChoosePhoto: String { return self._s[2629]! } + public var Conversation_Pin: String { return self._s[2630]! } public func Login_ResetAccountProtected_Text(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2631]!, self._r[2631]!, [_0]) - } - public func Channel_AdminLog_EmptyFilterQueryText(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(self._s[2632]!, self._r[2632]!, [_0]) } - public var Camera_TapAndHoldForVideo: String { return self._s[2633]! } - public var Bot_DescriptionTitle: String { return self._s[2634]! } - public var FeaturedStickerPacks_Title: String { return self._s[2635]! } - public var Map_OpenInGoogleMaps: String { return self._s[2637]! } - public var Notification_MessageLifetime5s: String { return self._s[2638]! } - public var Contacts_Title: String { return self._s[2641]! } + public func Channel_AdminLog_EmptyFilterQueryText(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2633]!, self._r[2633]!, [_0]) + } + public var Camera_TapAndHoldForVideo: String { return self._s[2634]! } + public var Bot_DescriptionTitle: String { return self._s[2635]! } + public var FeaturedStickerPacks_Title: String { return self._s[2636]! } + public var Map_OpenInGoogleMaps: String { return self._s[2638]! } + public var Notification_MessageLifetime5s: String { return self._s[2639]! } + public var Contacts_Title: String { return self._s[2642]! } public func MESSAGES(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2642]!, self._r[2642]!, [_1, _2]) + return formatWithArgumentRanges(self._s[2643]!, self._r[2643]!, [_1, _2]) } - public var Channel_Management_AddModeratorHelp: String { return self._s[2645]! } + public var Channel_Management_AddModeratorHelp: String { return self._s[2646]! } public func CHAT_MESSAGE_FWDS(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2646]!, self._r[2646]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[2647]!, self._r[2647]!, [_1, _2, _3]) } - public var Conversation_MessageDialogEdit: String { return self._s[2647]! } - public var PrivacyLastSeenSettings_Title: String { return self._s[2648]! } - public var Notifications_ClassicTones: String { return self._s[2650]! } - public var Conversation_LinkDialogOpen: String { return self._s[2651]! } - public var Channel_Info_Subscribers: String { return self._s[2652]! } - public var NotificationsSound_Input: String { return self._s[2653]! } - public var Conversation_ClousStorageInfo_Description4: String { return self._s[2654]! } - public var Privacy_Calls_AlwaysAllow: String { return self._s[2655]! } - public var Privacy_PaymentsClearInfoHelp: String { return self._s[2657]! } - public var Notification_MessageLifetime1h: String { return self._s[2658]! } + public var Conversation_MessageDialogEdit: String { return self._s[2648]! } + public var PrivacyLastSeenSettings_Title: String { return self._s[2649]! } + public var Notifications_ClassicTones: String { return self._s[2651]! } + public var Conversation_LinkDialogOpen: String { return self._s[2652]! } + public var Channel_Info_Subscribers: String { return self._s[2653]! } + public var NotificationsSound_Input: String { return self._s[2654]! } + public var Conversation_ClousStorageInfo_Description4: String { return self._s[2655]! } + public var Privacy_Calls_AlwaysAllow: String { return self._s[2656]! } + public var Privacy_PaymentsClearInfoHelp: String { return self._s[2658]! } + public var Notification_MessageLifetime1h: String { return self._s[2659]! } public func Notification_CreatedChatWithTitle(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2659]!, self._r[2659]!, [_0, _1]) + return formatWithArgumentRanges(self._s[2660]!, self._r[2660]!, [_0, _1]) } - public var CheckoutInfo_ReceiverInfoEmail: String { return self._s[2660]! } - public var LastSeen_Lately: String { return self._s[2661]! } - public var Month_ShortApril: String { return self._s[2662]! } - public var ConversationProfile_ErrorCreatingConversation: String { return self._s[2663]! } + public var CheckoutInfo_ReceiverInfoEmail: String { return self._s[2661]! } + public var LastSeen_Lately: String { return self._s[2662]! } + public var Month_ShortApril: String { return self._s[2663]! } + public var ConversationProfile_ErrorCreatingConversation: String { return self._s[2664]! } public func PHONE_CALL_MISSED(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2664]!, self._r[2664]!, [_1]) + return formatWithArgumentRanges(self._s[2665]!, self._r[2665]!, [_1]) } public func Conversation_Kilobytes(_ _0: Int) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2665]!, self._r[2665]!, ["\(_0)"]) + return formatWithArgumentRanges(self._s[2666]!, self._r[2666]!, ["\(_0)"]) } - public var Group_ErrorAddBlocked: String { return self._s[2666]! } - public var TwoStepAuth_AdditionalPassword: String { return self._s[2668]! } - public var MediaPicker_Videos: String { return self._s[2669]! } - public var Notification_PassportValueProofOfIdentity: String { return self._s[2670]! } - public var BlockedUsers_AddNew: String { return self._s[2671]! } - public var Notifications_DisplayNamesOnLockScreenInfo: String { return self._s[2672]! } - public var StickerPacksSettings_StickerPacksSection: String { return self._s[2673]! } - public var Channel_NotificationLoading: String { return self._s[2674]! } - public var Passport_Language_da: String { return self._s[2676]! } - public var Passport_Address_Country: String { return self._s[2677]! } + public var Group_ErrorAddBlocked: String { return self._s[2667]! } + public var TwoStepAuth_AdditionalPassword: String { return self._s[2669]! } + public var MediaPicker_Videos: String { return self._s[2670]! } + public var Notification_PassportValueProofOfIdentity: String { return self._s[2671]! } + public var BlockedUsers_AddNew: String { return self._s[2672]! } + public var Notifications_DisplayNamesOnLockScreenInfo: String { return self._s[2673]! } + public var StickerPacksSettings_StickerPacksSection: String { return self._s[2674]! } + public var Channel_NotificationLoading: String { return self._s[2675]! } + public var Passport_Language_da: String { return self._s[2677]! } + public var Passport_Address_Country: String { return self._s[2678]! } public func CHAT_RETURNED(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2678]!, self._r[2678]!, [_1, _2]) + return formatWithArgumentRanges(self._s[2679]!, self._r[2679]!, [_1, _2]) } - public var PhotoEditor_ShadowsTint: String { return self._s[2680]! } - public var ExplicitContent_AlertTitle: String { return self._s[2682]! } - public var Channel_AdminLogFilter_EventsLeaving: String { return self._s[2683]! } - public var Map_LiveLocationFor8Hours: String { return self._s[2684]! } - public var StickerPack_HideStickers: String { return self._s[2685]! } - public var Checkout_EnterPassword: String { return self._s[2686]! } - public var UserInfo_NotificationsEnabled: String { return self._s[2687]! } - public var InfoPlist_NSLocationAlwaysUsageDescription: String { return self._s[2688]! } - public var SocksProxySetup_ProxyDetailsTitle: String { return self._s[2689]! } - public var Appearance_ReduceMotionInfo: String { return self._s[2690]! } - public var Weekday_ShortTuesday: String { return self._s[2691]! } - public var Notification_CallIncomingShort: String { return self._s[2692]! } - public var ConvertToSupergroup_Note: String { return self._s[2693]! } - public var DialogList_Read: String { return self._s[2694]! } - public var Conversation_EmptyPlaceholder: String { return self._s[2695]! } + public var PhotoEditor_ShadowsTint: String { return self._s[2681]! } + public var ExplicitContent_AlertTitle: String { return self._s[2683]! } + public var Channel_AdminLogFilter_EventsLeaving: String { return self._s[2684]! } + public var Map_LiveLocationFor8Hours: String { return self._s[2685]! } + public var StickerPack_HideStickers: String { return self._s[2686]! } + public var Checkout_EnterPassword: String { return self._s[2687]! } + public var UserInfo_NotificationsEnabled: String { return self._s[2688]! } + public var InfoPlist_NSLocationAlwaysUsageDescription: String { return self._s[2689]! } + public var SocksProxySetup_ProxyDetailsTitle: String { return self._s[2690]! } + public var Appearance_ReduceMotionInfo: String { return self._s[2691]! } + public var Weekday_ShortTuesday: String { return self._s[2692]! } + public var Notification_CallIncomingShort: String { return self._s[2693]! } + public var ConvertToSupergroup_Note: String { return self._s[2694]! } + public var DialogList_Read: String { return self._s[2695]! } + public var Conversation_EmptyPlaceholder: String { return self._s[2696]! } public func Passport_Email_CodeHelp(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2696]!, self._r[2696]!, [_0]) + return formatWithArgumentRanges(self._s[2697]!, self._r[2697]!, [_0]) } - public var Username_Help: String { return self._s[2697]! } - public var StickerSettings_ContextHide: String { return self._s[2698]! } - public var Media_ShareThisPhoto: String { return self._s[2701]! } - public var Contacts_ShareTelegram: String { return self._s[2702]! } - public var AutoNightTheme_Scheduled: String { return self._s[2703]! } - public var Weekday_Sunday: String { return self._s[2704]! } - public var PrivacySettings_PasscodeAndFaceId: String { return self._s[2706]! } - public var Settings_ChatBackground: String { return self._s[2707]! } - public var Login_TermsOfServiceDecline: String { return self._s[2709]! } - public func Watch_UserInfo_Mute(_ value: Int32) -> String { + public var Username_Help: String { return self._s[2698]! } + public var StickerSettings_ContextHide: String { return self._s[2699]! } + public var Media_ShareThisPhoto: String { return self._s[2702]! } + public var Contacts_ShareTelegram: String { return self._s[2703]! } + public var AutoNightTheme_Scheduled: String { return self._s[2704]! } + public var Weekday_Sunday: String { return self._s[2705]! } + public var PrivacySettings_PasscodeAndFaceId: String { return self._s[2707]! } + public var Settings_ChatBackground: String { return self._s[2708]! } + public var Login_TermsOfServiceDecline: String { return self._s[2710]! } + public func GroupInfo_ParticipantCount(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[0 * 6 + Int(form.rawValue)]!, "\(value)") } - public func Conversation_LiveLocationMembersCount(_ value: Int32) -> String { + public func StickerPack_StickerCount(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[1 * 6 + Int(form.rawValue)]!, "\(value)") } - public func LastSeen_MinutesAgo(_ value: Int32) -> String { + public func ForwardedFiles(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[2 * 6 + Int(form.rawValue)]!, "\(value)") } - public func SharedMedia_Link(_ value: Int32) -> String { + public func ForwardedMessages(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[3 * 6 + Int(form.rawValue)]!, "\(value)") } - public func ForwardedAudios(_ value: Int32) -> String { + public func ForwardedVideos(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[4 * 6 + Int(form.rawValue)]!, "\(value)") } - public func ForwardedLocations(_ value: Int32) -> String { + public func Invitation_Members(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[5 * 6 + Int(form.rawValue)]!, "\(value)") } - public func MessageTimer_Weeks(_ value: Int32) -> String { + public func PasscodeSettings_FailedAttempts(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[6 * 6 + Int(form.rawValue)]!, "\(value)") } @@ -3076,315 +3077,315 @@ public final class PresentationStrings { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[7 * 6 + Int(form.rawValue)]!, "\(value)") } - public func ServiceMessage_GameScoreSelfExtended(_ value: Int32) -> String { + public func MessageTimer_ShortMinutes(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[8 * 6 + Int(form.rawValue)]!, "\(value)") } - public func Conversation_StatusSubscribers(_ value: Int32) -> String { + public func Contacts_ImportersCount(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[9 * 6 + Int(form.rawValue)]!, "\(value)") } - public func Notification_GameScoreSelfSimple(_ value: Int32) -> String { + public func InviteText_ContactsCountText(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[10 * 6 + Int(form.rawValue)]!, "\(value)") } - public func DialogList_LiveLocationChatsCount(_ value: Int32) -> String { + public func AttachmentMenu_SendGif(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[11 * 6 + Int(form.rawValue)]!, "\(value)") } - public func Conversation_StatusOnline(_ value: Int32) -> String { + public func Conversation_StatusSubscribers(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[12 * 6 + Int(form.rawValue)]!, "\(value)") } - public func MuteFor_Hours(_ value: Int32) -> String { + public func Notifications_Exceptions(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[13 * 6 + Int(form.rawValue)]!, "\(value)") } - public func MessageTimer_Days(_ value: Int32) -> String { + public func SharedMedia_Generic(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[14 * 6 + Int(form.rawValue)]!, "\(value)") } - public func Call_Minutes(_ value: Int32) -> String { + public func ForwardedLocations(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[15 * 6 + Int(form.rawValue)]!, "\(value)") } - public func AttachmentMenu_SendPhoto(_ value: Int32) -> String { + public func MessageTimer_ShortWeeks(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[16 * 6 + Int(form.rawValue)]!, "\(value)") } - public func MessageTimer_ShortMinutes(_ value: Int32) -> String { + public func Media_ShareItem(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[17 * 6 + Int(form.rawValue)]!, "\(value)") } - public func Forward_ConfirmMultipleFiles(_ value: Int32) -> String { + public func ForwardedGifs(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[18 * 6 + Int(form.rawValue)]!, "\(value)") } - public func Notifications_Exceptions(_ value: Int32) -> String { + public func MessageTimer_Minutes(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[19 * 6 + Int(form.rawValue)]!, "\(value)") } - public func ForwardedPhotos(_ value: Int32) -> String { + public func Conversation_LiveLocationMembersCount(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[20 * 6 + Int(form.rawValue)]!, "\(value)") } - public func Notification_GameScoreSelfExtended(_ value: Int32) -> String { + public func Media_SharePhoto(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[21 * 6 + Int(form.rawValue)]!, "\(value)") } - public func MessageTimer_ShortWeeks(_ value: Int32) -> String { + public func ServiceMessage_GameScoreSimple(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[22 * 6 + Int(form.rawValue)]!, "\(value)") } - public func SharedMedia_DeleteItemsConfirmation(_ value: Int32) -> String { + public func StickerPack_AddStickerCount(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[23 * 6 + Int(form.rawValue)]!, "\(value)") } - public func Notifications_ExceptionMuteExpires_Minutes(_ value: Int32) -> String { + public func MessageTimer_ShortHours(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[24 * 6 + Int(form.rawValue)]!, "\(value)") } - public func InviteText_ContactsCountText(_ value: Int32) -> String { + public func SharedMedia_Photo(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[25 * 6 + Int(form.rawValue)]!, "\(value)") } - public func GroupInfo_ParticipantCount(_ value: Int32) -> String { + public func MuteExpires_Minutes(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[26 * 6 + Int(form.rawValue)]!, "\(value)") } - public func Map_ETAMinutes(_ value: Int32) -> String { + public func MessageTimer_Years(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[27 * 6 + Int(form.rawValue)]!, "\(value)") } - public func MuteFor_Days(_ value: Int32) -> String { + public func SharedMedia_Link(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[28 * 6 + Int(form.rawValue)]!, "\(value)") } - public func PasscodeSettings_FailedAttempts(_ value: Int32) -> String { + public func MuteFor_Hours(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[29 * 6 + Int(form.rawValue)]!, "\(value)") } - public func AttachmentMenu_SendItem(_ value: Int32) -> String { + public func MessageTimer_Hours(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[30 * 6 + Int(form.rawValue)]!, "\(value)") } - public func AttachmentMenu_SendGif(_ value: Int32) -> String { + public func MessageTimer_Seconds(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[31 * 6 + Int(form.rawValue)]!, "\(value)") } - public func SharedMedia_File(_ value: Int32) -> String { + public func PrivacyLastSeenSettings_AddUsers(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[32 * 6 + Int(form.rawValue)]!, "\(value)") } - public func AttachmentMenu_SendVideo(_ value: Int32) -> String { + public func Watch_UserInfo_Mute(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[33 * 6 + Int(form.rawValue)]!, "\(value)") } - public func PrivacyLastSeenSettings_AddUsers(_ value: Int32) -> String { + public func Call_ShortMinutes(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[34 * 6 + Int(form.rawValue)]!, "\(value)") } - public func ForwardedGifs(_ value: Int32) -> String { + public func ForwardedContacts(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[35 * 6 + Int(form.rawValue)]!, "\(value)") } - public func Conversation_StatusMembers(_ value: Int32) -> String { + public func Notifications_ExceptionMuteExpires_Hours(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[36 * 6 + Int(form.rawValue)]!, "\(value)") } - public func QuickSend_Photos(_ value: Int32) -> String { + public func LastSeen_MinutesAgo(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[37 * 6 + Int(form.rawValue)]!, "\(value)") } - public func SharedMedia_Photo(_ value: Int32) -> String { + public func UserCount(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[38 * 6 + Int(form.rawValue)]!, "\(value)") } - public func ForwardedVideos(_ value: Int32) -> String { + public func Call_ShortSeconds(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[39 * 6 + Int(form.rawValue)]!, "\(value)") } - public func Watch_LastSeen_HoursAgo(_ value: Int32) -> String { + public func ServiceMessage_GameScoreSelfSimple(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[40 * 6 + Int(form.rawValue)]!, "\(value)") } - public func MessageTimer_Seconds(_ value: Int32) -> String { + public func Map_ETAMinutes(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[41 * 6 + Int(form.rawValue)]!, "\(value)") } - public func ForwardedMessages(_ value: Int32) -> String { + public func ForwardedAuthorsOthers(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[42 * 6 + Int(form.rawValue)]!, "\(value)") } - public func LastSeen_HoursAgo(_ value: Int32) -> String { + public func MessageTimer_Months(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[43 * 6 + Int(form.rawValue)]!, "\(value)") } - public func StickerPack_RemoveMaskCount(_ value: Int32) -> String { + public func Media_ShareVideo(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[44 * 6 + Int(form.rawValue)]!, "\(value)") } - public func Notification_GameScoreSimple(_ value: Int32) -> String { + public func Call_Minutes(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[45 * 6 + Int(form.rawValue)]!, "\(value)") } - public func StickerPack_AddMaskCount(_ value: Int32) -> String { + public func AttachmentMenu_SendPhoto(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[46 * 6 + Int(form.rawValue)]!, "\(value)") } - public func Contacts_ImportersCount(_ value: Int32) -> String { + public func AttachmentMenu_SendItem(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[47 * 6 + Int(form.rawValue)]!, "\(value)") } - public func ServiceMessage_GameScoreSelfSimple(_ value: Int32) -> String { + public func MessageTimer_Weeks(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[48 * 6 + Int(form.rawValue)]!, "\(value)") } - public func MessageTimer_ShortDays(_ value: Int32) -> String { + public func ForwardedPhotos(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[49 * 6 + Int(form.rawValue)]!, "\(value)") } - public func ForwardedFiles(_ value: Int32) -> String { + public func Notifications_ExceptionMuteExpires_Days(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[50 * 6 + Int(form.rawValue)]!, "\(value)") } - public func SharedMedia_Video(_ value: Int32) -> String { + public func LastSeen_HoursAgo(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[51 * 6 + Int(form.rawValue)]!, "\(value)") } - public func Notifications_ExceptionMuteExpires_Days(_ value: Int32) -> String { + public func Conversation_StatusOnline(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[52 * 6 + Int(form.rawValue)]!, "\(value)") } - public func MessageTimer_Months(_ value: Int32) -> String { + public func StickerPack_RemoveStickerCount(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[53 * 6 + Int(form.rawValue)]!, "\(value)") } - public func Media_ShareItem(_ value: Int32) -> String { + public func Notification_GameScoreExtended(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[54 * 6 + Int(form.rawValue)]!, "\(value)") } - public func Call_Seconds(_ value: Int32) -> String { + public func MuteFor_Days(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[55 * 6 + Int(form.rawValue)]!, "\(value)") } - public func ServiceMessage_GameScoreSimple(_ value: Int32) -> String { + public func Map_ETAHours(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[56 * 6 + Int(form.rawValue)]!, "\(value)") } - public func UserCount(_ value: Int32) -> String { + public func Watch_LastSeen_MinutesAgo(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[57 * 6 + Int(form.rawValue)]!, "\(value)") } - public func Call_ShortSeconds(_ value: Int32) -> String { + public func QuickSend_Photos(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[58 * 6 + Int(form.rawValue)]!, "\(value)") } - public func Media_SharePhoto(_ value: Int32) -> String { + public func MuteExpires_Days(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[59 * 6 + Int(form.rawValue)]!, "\(value)") } - public func LiveLocation_MenuChatsCount(_ value: Int32) -> String { + public func Notification_GameScoreSelfExtended(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[60 * 6 + Int(form.rawValue)]!, "\(value)") } - public func Map_ETAHours(_ value: Int32) -> String { + public func Watch_LastSeen_HoursAgo(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[61 * 6 + Int(form.rawValue)]!, "\(value)") } - public func ForwardedContacts(_ value: Int32) -> String { + public func ForwardedAudios(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[62 * 6 + Int(form.rawValue)]!, "\(value)") } - public func MessageTimer_Years(_ value: Int32) -> String { + public func Conversation_StatusMembers(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[63 * 6 + Int(form.rawValue)]!, "\(value)") } - public func MessageTimer_Minutes(_ value: Int32) -> String { + public func AttachmentMenu_SendVideo(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[64 * 6 + Int(form.rawValue)]!, "\(value)") } - public func ServiceMessage_GameScoreExtended(_ value: Int32) -> String { + public func LiveLocationUpdated_MinutesAgo(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[65 * 6 + Int(form.rawValue)]!, "\(value)") } - public func Notifications_ExceptionMuteExpires_Hours(_ value: Int32) -> String { + public func SharedMedia_Video(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[66 * 6 + Int(form.rawValue)]!, "\(value)") } - public func MessageTimer_ShortSeconds(_ value: Int32) -> String { + public func ServiceMessage_GameScoreExtended(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[67 * 6 + Int(form.rawValue)]!, "\(value)") } - public func MuteExpires_Days(_ value: Int32) -> String { + public func Notification_GameScoreSelfSimple(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[68 * 6 + Int(form.rawValue)]!, "\(value)") } - public func Watch_LastSeen_MinutesAgo(_ value: Int32) -> String { + public func ServiceMessage_GameScoreSelfExtended(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[69 * 6 + Int(form.rawValue)]!, "\(value)") } - public func ForwardedStickers(_ value: Int32) -> String { + public func MuteExpires_Hours(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[70 * 6 + Int(form.rawValue)]!, "\(value)") } - public func MessageTimer_Hours(_ value: Int32) -> String { + public func Notification_GameScoreSimple(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[71 * 6 + Int(form.rawValue)]!, "\(value)") } - public func StickerPack_RemoveStickerCount(_ value: Int32) -> String { + public func Call_Seconds(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[72 * 6 + Int(form.rawValue)]!, "\(value)") } - public func StickerPack_AddStickerCount(_ value: Int32) -> String { + public func MessageTimer_ShortDays(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[73 * 6 + Int(form.rawValue)]!, "\(value)") } - public func Notification_GameScoreExtended(_ value: Int32) -> String { + public func Passport_Scans(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[74 * 6 + Int(form.rawValue)]!, "\(value)") } - public func MuteExpires_Hours(_ value: Int32) -> String { + public func StickerPack_RemoveMaskCount(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[75 * 6 + Int(form.rawValue)]!, "\(value)") } - public func MuteExpires_Minutes(_ value: Int32) -> String { + public func Notifications_ExceptionMuteExpires_Minutes(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[76 * 6 + Int(form.rawValue)]!, "\(value)") } - public func SharedMedia_Generic(_ value: Int32) -> String { + public func LiveLocation_MenuChatsCount(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[77 * 6 + Int(form.rawValue)]!, "\(value)") } - public func Invitation_Members(_ value: Int32) -> String { + public func SharedMedia_DeleteItemsConfirmation(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[78 * 6 + Int(form.rawValue)]!, "\(value)") } - public func Passport_Scans(_ value: Int32) -> String { + public func Forward_ConfirmMultipleFiles(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[79 * 6 + Int(form.rawValue)]!, "\(value)") } - public func MessageTimer_ShortHours(_ value: Int32) -> String { + public func MessageTimer_Days(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[80 * 6 + Int(form.rawValue)]!, "\(value)") } - public func ForwardedAuthorsOthers(_ value: Int32) -> String { + public func DialogList_LiveLocationChatsCount(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[81 * 6 + Int(form.rawValue)]!, "\(value)") } - public func Call_ShortMinutes(_ value: Int32) -> String { + public func ForwardedStickers(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[82 * 6 + Int(form.rawValue)]!, "\(value)") } - public func Media_ShareVideo(_ value: Int32) -> String { + public func SharedMedia_File(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[83 * 6 + Int(form.rawValue)]!, "\(value)") } - public func StickerPack_StickerCount(_ value: Int32) -> String { + public func MessageTimer_ShortSeconds(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[84 * 6 + Int(form.rawValue)]!, "\(value)") } - public func LiveLocationUpdated_MinutesAgo(_ value: Int32) -> String { + public func StickerPack_AddMaskCount(_ value: Int32) -> String { let form = presentationStringsPluralizationForm(self.lc, value) return String(format: self._ps[85 * 6 + Int(form.rawValue)]!, "\(value)") } diff --git a/TelegramUI/Resources/PresentationStrings.mapping b/TelegramUI/Resources/PresentationStrings.mapping index 611be28fcdad18710b6c6e9b63ebb48e135c755b..0ba06eaac5ad0f917314f71c21cb6366f1385bb8 100644 GIT binary patch delta 11107 zcmZvC34D`P)_zamZJTt3wrP?kY0`A3O)2{hr7ckC2CWM-3=rB-B5hNWRKx{!=5y3f zLGTQmZ)Q|r7PnD^`Nm~j(V1};N5KUV0ojyQHdj!A|8w7@MZfv`^GAB`Id?njbI*Ox z{$6|ZVU1)fz4R89@!Balj@nQt7@8gm_Vha;gtTg2ZUhyNaqkBf7NFQ4q?GXmWa zLf0ec%EWKAud}noABhCIJHp;p-=eyX@zs)If4DdH>WHsiN{4Zijr7yX-8yi-VT^2T@aZQDUNV$9TF#M=__g(CPnZU;I``0d0T-hL+8RJADB ztIPjwPJ6eQ-r+Ity6Ij1(Yr2sk8gUnkaqHK-p$W`Kem_Vy2i%ZngumY)oL%hc;CDE zjt@XfN2?~_>kM|(2f`6=O~|)cUu-u!-y5lY2##`IJI2Ai?`6{-UiF^Wus5DJrDgDr z_soWU@%+|g%l$huwU5A7UHy67O^x-)O=Auwfu(!3AI?rGUb>;?N zkGFbBFc|h%HFx^j{EF{fyi7Kejpy$gM4xfbE*E{yYj@>4zr>h9pkL#Qv@P)}ohYY> zY2_<^cULie&EM^E(0R6fkYl)@TQfI(5KrIo)gL&u?=U`-9e>Z}H6IMrE}_+DP^om8 zU-+QV5SPG*KPV(()9ySPz~#HWG?3@*_E0?Ewz~i(_V8}EAu)kzUWPPJrF2)9+OIJo zE-n?n%JY<&M0&$heIX?=6DNOIq$PoF<1I;MZu~GwOGYbKDh7~+um3RBo`Pl?SQ~=f zktN=iCBaZ+W^d;T^u(p|#t%nR8h`L%F{Lx@aZm>5?io*+{Np_?%Ho^%cr^=#TiLfJ z+teC}bn4=>@{T>DDTfpHx->h;c9~R5mgMRszCM$1@a(;%l*>2oEt=>CM+O$VCeYsD zk9ezkBTMG^m-PlhQMbzjS;>4_FtpqkYWKG*hF~bHJD7(L?DbMUr|m1E0v^53O@%yn zpNES0mVLQY!Vm6qA&$Mg&pNRj(M3Msgm2!F+4$8iG??Ql%b8lBsTVKH1k^@B^gF0 zs4$l3*Bxk--bnE4F>ox~_Iqd?PugEh6S!l)N1K9?dAc({zCTr~Ld(swf0NDG+Xm58 zKC-`vYB}|Qjb?Dk0Vmb*j00u%nc(^lm38ZQyQ_La z0sTac3B2iVIh=aXtTlnj$+uS6ujsFtn+^`Q&yFo}jo-ht-M7Nq+UpPF7m|vb!w(%C zMJ>GZU?sJ3!XXdM;n&4WXa#3j95ve|^YG3;1bJ7xL~yrHMXFXu)W%2-s;6 z7a#Ue8`mFpQadj_>>@wki+>mMlZPv*gZCdEN=rEXV;2Ru@?$4m$ITzRX(@+3E~QR> z@Z&;T7p6A6F@bkHmCbvOWYA4~2D~dd?PxjO$`g;4(QSO)(IIp@uRoek zKjjyWy6I=U?`R&~asRPl+MSrLbe)Crc$VnqIJCDgp1bm zuTD5f__-6g+AsCBnr_K4s<^F2JaGTX;q)MnI9Wgs@sCcrXanDL(r(=Z#$=FN16_V^ zYY_HRrRS>O@RpN9=(oK0WS-%7(L<$#P3-v8M2~XurzO>oVW3J8(NJ6O3rAY~{%&to zq{bKVD@8sYW2g>xU+)iveUU)0+gl%O)BgZH?hpKnPYbmtz-Hlve>Yz-?1G+c$U*oo9THjI$cCBaMS5x?L{@KtnnZSw)orBPSpmt@y64YiLZjD zP!rcbWzqhEhJ(wuSvl=Y68)8P&J@z$c8`6*bQ$aC}c9KQc3SE47m~-^q}7m_3iDkg<6BX zA#bbyhRATR$L-+Ovo3m@d(RdCVf^}RsqJ0xDfd$wst@|wb)cwN-{bvfJ%;zAQE#zN zb?su$xgz?IXPwJ4>`CAQE!lkZskDr%dO9(%rOl_GWiKYoipukab4G1Hnl663I+ORE z%K&;fcg}4%2%C4AWy=60Mek?nbciQ^W~IYC_p?0um~Z~99N6IT&+_RAZ~v^Aj`Df* zALE?QU54XP8*BbNQ#*l)J?y;J%(s4?X*ij{%YK!`kAI$Q{uBgd{nf#)F4$6VvD*46 z{`cn&K!y{ad*}?Oeo;nedHfe;+Bq;6$=fMNb7Njh(}Tfh{Od0YU7v%X(ph6L5?CCF znqyeuglIPSf)9RCPG54`mt}^p5jS3T0>mZoqAxQ>oySOpNUCR4wJvC=ZE2~RUfYM$ zE`X+_9d(DQo}Rv7_YMEy%VNW~dOYJ}Uz+J6U&gcyZ zWcNK8yzwhL8Th5IvJC?gdF$O7d|ySfu{sdZ4R|15`pTneiGT^bNi00+>r}+ahOb9z ziRg3iT9e(_8@mS>`H8Q~D2aD}?a-1zwMR9WdOn>}IQM)m(8J{O`IdB$R5XrSl>!PQ z(TW?yH=Qq`!TkI4Wm*PU+zLzciSwBN7njeECM%D=PzE5@ejyKnzVm{6yd7N1)&b8K z163;!))yijASubWL;`I~5fZ#Dy&WCEXEAi>Y4{ouk~SFi{V$XTOQpioun|^#?2t zHDd`{D(=MqZ1aq!)+RNejE8<(td%QXxxIoiP3rQb26&;D4H>-p+YC>IqN1z+DpDQn zS)p=4cPzmT1+Uk_d%rbnmEg@2cO}_VYl3aPUHhWpIia&fR>Y$ETt$;`K0%m7ANd$AN4<;9CR z+9b@7!BxM@iq}!sWd8bMK2@>(k`)kT)Fr2_8Vq_ch*@p_d26`)k{t-nSbwYBUw7tcFBG zK5xsiUSG)H5Ny}+!!?QA_?xnIXDOMkU3ZZxXs(nK<a%;%n73|bwOhhgXBu`;4X!Ize_v9+$X zzIH)vOmg1Ka<_@ zQ0ITick$$=JH(klx%6|Hlt5;>Q?5#&3hgdTRlwaxJ@P~X8R>3$CV}#_d(bjmo?_#Lmzs-yqQQvb_Pv_z`l4?*LZbPU3IOJ#4jY>NQH)VaO0Pqa9({fLsE<;XxQqnvK6%l%%QNhor_t zUfQ4^F{w+>51TOB%&Rl3vc*ILJ-0%Y$7+CoN`?XIq5mMJB94@XL&e<3h8-yGlj~v7cfn<;^poGy)U8*|HrB`(aiA@ zIx72&g!j)#rku ztiH4FR6UqQuMNuV5tLP?F8Ce(zI6OP7?jJlE)TXu{5|U0?yc?eMK7Sc+@W<6rFS?ToYpd!sn}I4Cw=uqR7C%|PBbAr~^R;ge#^BwOAo z5dW_v`jf>fqpf}OXSQ;MSERR=u>fbejk^wWMOmos6)pNlgKdGAYbx|DNd$_@`D^h9%z}G;E0?k`(Rbo7<0zM8l9|fJUB-M$Q!!lA*wj~=Q^+|3g!*7dpgXGP zYUIcgdx|woq12ICMDu(3rIwn}-G`Va>LW(!?x$84zOg0VH z62R*c*G#fWAe&OGiD>^2P7YTijYip;P8N9tJemnSG1o%L(n?8kI-5L(WTUFhBn9;A zQ3`mI9L3MLltwpBTC>;@Owhb`C zEgd!hjXV%b^z`_M4N5AIZ8pTfA~|M*u8Ji!2MR0^Zw|73slE^C$)R{{2!<)JX4aJ8 z%Vd2HkV3g^%c0?#7qnOM+!>rR?Dc^$o|UqEWNZnT9Ck9g=0WfnLm14(IbS43RliO77RIzkSq%w$gkt& zItOC@1i8b3b58`(q6+P@%>iGUByT!!(#dklfyt-HeNHMiRbuwIDlleq?wM#UW~$6{ z(lDx44CPcKFFAovYQa*fFo7iIV%yWuRJZ8pfmN|j%`;t>UUhe;YRmwO@~!@oL(}5L zoq=d^tq!zIMbnL3+4r>0VDQS-cc#9%1l<e|xSth%vK${H~D|g>(mRnt9vdlqKX{luiKMTD%1Sq)+>i7bQGk+{+{mZ%qBg!bq;~%k zIq4>=VZKpMM9w@UnFTU5kA~1fnU_b_35zgT#X5bn4gT(4RFtvpRvj2Fj_oOiX}rw= z6olKS$F4OUvQN?~itA6qExVct?*Oe2i25(Px5?l;y*dBl44cEY~Y{tDo!T>3mq(a@na` zHwf(C4k+p@pmBhra|^J@P10L{#CWqv0eKC#7&(2jS@snm)T~r{RXRwnMnZ^$bd+?f zq!-dCV9)7=aKPJTSs_IHQ(0FC{Pi;oaPYEUWXkqJ`2El2Kp{ZZosw3B1@4l{A}R&w zoL2=1ppQnQyTq3dWxyQu+FGIBRpP=kR!4UUD_|fn6JQ#B$NO-{R&NsI+s+GkcoaR z+setA6nzs@>pUQBCD`yoaz_cywm}{%f!H?6_7XVrCOJ`pbN^N{OL4T{OLZw)4@*}m z*|kS7ubtPnSkt1VN;TJG=yQpCBJ}-cDWzvVjy8%OdVL8sCdKjxIaf+n^An)p|Gqj+ z(=0`MQd~o@_-2_l1ZsRr77s!0+9G!hfq8G0M~9GO#2+z8d3Lq_@>ku7%vsyj6D@iC zFKB(`wP;FJlm5vl6{EUTFBAvcU^6{7YW(lE?W5 zsLCg!rNL%D5L5I|b;f_MufT!cG~xj-dQfSqfV;gVb1R_J9db(r^!BzqQUU3{BX6qC zcja6KqS||69EyyvQ!0mIhWCx^-EHO_n-k@xp_FFWW#lPavJ+}`^ZY;_ABz3#mVXT; zr{P2R@N?$%C|VEzUL_4N>^0(1H%I)HG{~^e$gj3%$UT*m?D_~h*RMMLFBrWYyhIu=Aj5{hFYkRw0xGlaBL#Qbi_7A5Do` zA~^H-wmoJU4iIKAn)u9X)@uEEE|xgdFcY|xQP%{QcdIfozBY)yd@5oRh&Zuc5f9^8 zSGN%-1=xb}U41mp_k%CLAW2Jq$CS8A~S3iGkT%`AF84 zCyQKhj6;BTnRw9eGS-eWrM0w$@Ol!)3#VGjEj{C?M9afK8*g|#E2Xb=sE8ifHV#IX zFQ>;LbQZ{<@kkGa(o+RL7(X7drC6FL;_n4`26mOiHXX$=D4h4;9Ua%P=%uLCq*oJd z#aHefL*%9L2x4WjcRX@ixm+BNbl}xDf;5xT03g~Nt9A^PDHFg`DZUBV&oH@f0@C1c zd36Fn-U#_(0z%>_aZE&OwAxWIjgb`-q2F<`aUzX!j>n|RKKcZ%yG5U{C&(8QDbF?$ zJo>$}FTC_Ud{2_0lc-3W40?g8!boHiT%=0wo`mRJEq|VbU|1vjCn31hifJ-A+|$6S zCztO(M*-g`kEUv9ELyk_9z1oWjC2sHXM8+Xj%;GM|E zT}y7mZ3sZPCZ>Pywf1&>Cp%o(G8r$-hM$`5UuWhiGc~z>8c^cTCMk0e?BnCu-IJX%4b>-nKoUX7 z?TN@w>!1v)RL!BR#D+jOUbMnSS=NYsnB>GP(2`_hJ)&)L9MkFyUU_ePdQ0!3a9b#V z@0G$zh8C62>}r>)^r6eDJt{YRY~jbpa%s|Bhtfm3bkD-HgJgCSnawK7tI_>iogP>m z&XC4t$}wbOqj;V>{Xnu@m_f-_l`?c6xS|bR6K1)&3HFsOy|WReEoy#i{0uzVhON@o zK$cMoZm3N);8?Xc^x$inus3$d9CWKa#6+i_=0o1OK*M3Xxa%l;sCvQD!y?9FlU{vZ z=l^e%PRW%Q8z@I}fx)E*5o1+*vai6=(rmqcHL%Lkzt z!nMhkW*RiD08Mq6{yTYWd{sxV5Zx-=D09{?UqxvAa4}TZgWc`nVp-FOa8M#c>ygn) zW!@Ze%oqZ0T};s`dsG&BzzLUu^nXOJ+f+IF)fKE-ohG&suRJ;vA-qCvhZzqa3hI9d zSb0de5|kfUsjkfMus9trUiKT+d6Tso--_^XIR`yzDzZjv0SjhQJjyRkv!VAJ> zoGN)^4z@m3PB-FQ)!=c-ZB3Mu(l=?1-ldjG#ZoPSE)+;=(U-5-?B8F0n!G$4xNN$V zHOwv>BH%^?8I>eRL3>C8kDrPd%?f;a>?}PRxeW zA8o>J8)eljWcMaHK9fAQW{g!v^#hkt6VDd=OhAu0Vpkh#QT~%*RFSF`+_Bga+tO7k HxaIyoGLOpF delta 11205 zcmZX434D~*)qc*%o7u85NoFQ9nJxP^5OxHCkOd-SApvngVMqp&lFWoGEUwsUwTlX# z%IVjtNEhn{LanydBJj1fS{FbB5oHw@L^h=gDDZ#oJ4vAb@Ar!i_nvdlUC#F0bKj?) z)K2|gqtzQ}(>s*Kn;&tTr?y3UC@wDUNp^4Z(o;Ns+X#A=7jMheo{Nu*%i^^Tds0(4 z(o!34X$yfbZXIs{<9a^0%}*PcUiPQAwS=R!;qLCx!f?0Z*oc7vUSahm&1~yw538;h z^seBe4vt(TJdDHmcJDi5C@x1aShkKaX)!#v};J3Q1 zJ>1lGYdAPr^}fc=cT4DXu6{RHdjlK}-srX^PHvAjFRG4qfg$eSyz*Tiy~!Kj&7-$? z&$~I9?|{z?%H(KIu&F21)uY(9p|g;8m|R@8Go7~cl$`~%gFAQn1Mh;84vwa9PfuH9 zVRx`1)ZX6TP(yd}3p?}ZJ>I)Bhj#I$o#_d?F~kW`SjCiyzjjX!-w&_Hrd7#Z5k);N~Mq3yQ`G;aqX@= z+Rr_^{B(d<@A7E}G04uVJn6iBmzfUn-d#S&Vf3k^Zm6lNtE!wkv+DYp>YzU6!Mg)= zgh%eqH+%x=zAV0NccS(wxSd>oM+RTKAzlMt+-JOgx0gQWcXsC(j>hwv949BfZ_M^gDHL09S(K6abM}8?_A?BPYt(s zs-81^;(a&$hpl@G#(x9qVD#aLB9U-=uzY^Bww`RsytmM0 z0(VkBcTCviP?tJmGgt1-H&~34s*x+HttT4MJ;sUwUU|Su4qm@Ei4u6z-b_QHk;`82 z^4Yz1O5&6c3n-b#ewd{V2JcYbF)?%1(PZ1K&h}`irKvfjZ`B4;x+JGjdO|p&+iV(o zvUyEr2EX%Rnr27K$)S~YzWAY8bD&ki4?dNfT(zt@+}YC>jp%-w$&)_vk&8nga zB%`llYWd8$<&~9lYpZG}S2ZfODI7ZJp{cy$UXhr9wwt)v`&{*aX#_?1JM+6;6SbMR2nfNL~z;^8uC;)#c| z>3VKH?ALArkB`g$mBDKcr_vnWc-Zf{8GY&u8$t;6h+S$IUAkIt;o}%Nms3A3qIo?2 z<7^7?Jp6w?)|@u9x~r`vv>XHDnjt|2v+2==ZIR$)<$Em+tuWNd(U#?ElP!vYhuG42 z-^WHRj8+~WTA#)#Yb|U%lB_L2PcBct;N%fUY}!J_EvdQWfWyYcwHi6~q9cB|64b`` zAMw)<_?aU`w1{^d$)k4u{)j{Cz>pkXoSeoFoi*|GFC;r6=ur`?eAdjVa~rFs)YR80 z0a2dwNs*xw2bye?gI+SG!G+rFxCQjM#rjf$b6kVVo0W-QVGmvW@h5&)HyG4$ z)kK!G^@L}_N`tc+r>mho?ETbFyNo$YPSA?46PWx#5(pBj1912)b-nr8SJ#16G_!_j2=InO)l z*B-z)cYpBX$B$YK4;nc*Ez4M@AL2v2?dWiAC7AL#VRkN;d|{?lJnD;VdYEtgqEKT{ z{OnJ$^O`SG=@-253!nE9^r_fAJJi$M8tlUhdRkPASF#I-l6p7syYfYpb@mnqF4?OS7Lc>!=c_NmOP4In68)J7vc zt-&eb$hDzJZ>UT6u#KQO`Hq_${LXO`y}<7u573|Z{PAphkv%609WQ~dtbG8-)apRy zuRQlezTst~?Ddhyf@~OrC9m*?6Zt?$drp+m7Pg%9(*JVN$pZR2H=WGW{-Nd)uMJ_d z-xU7IYfh#+UjrdQEii`Vf@(4}u8DdV_tZ+=hP}71W9^Q=a!W z&pG9^zXL7>xMoIsyMoGjhARo%_~)m5z(?y&3sTte{?!v*bBdDvh%B_v$PK(H(eH|lkM`c?&k}qivem1zw#Rn!h(GnJpZdS z*CDX#Iin9G#ImiDeVA8$70^BgJ)q21_pu{<_^TZHgpFSp(5F1~>p=ErYASHgTpDfa z33rzFLR(cGp|65yEIrN6$UNpSl0fVuKaGM(Zl&J5E|qtC@KEIxiF zfxhDNXFT*ZJO5Lvozb}w(~_rjMSDA|d)xJ7_zmLFs~*mLBPnrKw?aa(#Puz&|BuUa z7Gn~zmY6#tKJ?qdIS{hAbhO*j58lt?Y=J9%-()zx2T|E!ZB9;1j zibs8$tqlURm)DAemwcOKh&RdUavEaMZOI5y7O%3n6MK7NA>D)?KR=t|wdmW2%s30D zp3O2?F)o`jxcY1g0MzWWUM&f%&b~a7;7&$M?IvcdYP%!V;1qOw_?B9~ZXl_=?`(-S zSf4{(F#_k(v^2Cz_-$tvUwr zj5ek5itkdWgjap%GnC?F*VtrI1I6=O-z8Fz_o8y5`#wV(ia`OUJJO`1j7&W3 z`$Q_^8@>+$5#9g&2pZ0tzArY6FiBD+7G%9Jm`1YaLLrUfsTYb3qfNZ#p$z`ng~5g~ zCfV1RioR$2AFj$`5Np zox#esu5j}JoGj-ExF+)h7abETz@^7+!07H^BjR>s;ebn2f~Z`vsWsFUj$J|Fik&^n zRb)YbTooU=n5$KT&8w%I+)K&XQ_xag7DFoyp%_%@citLM101`FrUHxIeaQR|EMWcyB<8u>{hWlq}f>F=6 zKeC!KXyzm9XeiBkWIwqL*O|EPHziU)l%UOq=o~KF?2s8mHo9KIL;<=%?k8mb8$rzG zjrV8BYeZ>=n@qg_Ww)FqN~bxJWT5PcH-jTh?K0He(-aOzg0tItTH7M!v%}#<=#IOk zZ{@SwS{8ezwJO4uTJM|lk+yy>r)laPDRwb~_q+^eU zql0A2Rg^+MkR4Z1v11X2D2MEenst3zZkMz{lusQpeh}qQL_&jTG)3iq29cjS<&{B{ zON-^uAPQ2Kq-bb$%P5VqsRuq}zr3I&kRE{ZJNWpbM19{&oSu4(=@3JczPP)uCl-H+M*9JSM+W*2LqK?vgF>R6uvj(Rhsg zp`;q2*B{9UBShRI*BQy{xVLYg{b97SzCwkJdemS8={!~&;G>U$v zPH$YR?qxs6aCP0#YcdTwo`-@LC=n0zZKdx7E4sp=9(~M%@~DYC^pLz_!a7#UJ`)zV zN;ETm9+pxwWonGEE*%(HD|)+oq8*SI_X~-dX)HY=Pn&V}tK}UtwkUGa3@N{qR14*} zf8Dp70aGmRsl=>mIgiQ=3l-60a=Qfw^tjrCu|xNtC*(~FMEpjMTPTZuD^@GzX}^P{ zOm&}>dMhmJ_h{zw=1#BNW~D@WQhs8kA?`o)O;@3pg(}){*{gnBu#1);f62j*4&|c)JrCAuy}ZPi7g>miHk<;#Q!W?6Uj|~k^PC-+F$kY zr|smj#SXVWRJ^RO3#GWM{+?IRgTKB~Q?yg5;cwEJMB{0btV<#-QWEyL zMf}M$M%#)pd8h)UOLsCk41YKAyuV~v8|!D)RVr2d!^CSN4tXt^Qni1AHBE>2D<2-D zd&0luY%+PZ*FnhV4WnE#I)!Z78)z2FiizZvND8IWzhy-VwDqPuo`UJ$lI9FzRCwbKnVkwl-YK`H!n)s+XVlLw*_(>f*)89uVnchxJ{acp zfs7wa4s9@u9zG zT2O@ocMuX{7dC7*j_8BI~0kUw(c^hKV z=VDF6(4$h8Mn3l!AnI5ASk|j<4|TWJFNnc9=}1FdI1UbE>4_O~#}KmUSKAXN_Eu!4 z%xvojqfn(*dlF0~{Lu+FZ^=lMlytJtDRHHfi%!evbn-a90>c#{aB`?w59ep3Bb|!r zKg#O-u`=s7;MAja3}QCH51L!m9N(JQy4)#Wrjs?}ESRpqp>;YcJ12=5h*0NcR0cxA zcQPx3e1;1qy&^aC0ZWdaf0fN#?3<#mELd0HUl_b(l1&+~gg7bBp-l6%@N(S|40168 z+Pq3UcAWMg8EvN=gJ$N_OKj3<$4SS_kL=inQJ%L`hGxRbVwQ8j4*xdGaXXD6i{v>d z+h8@T>WsBbzi=ji6PJ&Pxyk=s-b$3;fLBY=$$m$ceC9yP9V{0euqK;iJ28KnSz%C_ zN~hTnCz1^nOLnK#6p#a-kW@Ei8cNObs*&y9%PaJf@yI@*12@%a`b~9|j0OCq(Q+exv@u{p&HPSh z?f}%g8ePi2bU3MwAlelK>OxQ4IN6kiY(8ENsh?{^^N`m&0sNQC3F=1G7+%~9n5k=H zqLczW$Cs($lVq`n0$Mo+zu5!$Vm@rS_NpaT5r^9N^58jHHTZH?JeOh zHNFzWA|0qr@{-eDg{H1ry`0vstZM1>!t$rcN-unQsywG!HL+C;S5l_Qc`ps4>8h)g z>f|QWQ>k8-`k>+ldCW&Sjv0`w^r55O%lE=Y`2c&-nm|QK2I1Z1rzDywMSj@ZESc)3 zJi1Pze(Z5JcpN-qSEg+6V_Vmw>E!*7y73zqcY`jV;f9RZEwR5ga+7XCfd%?r=YU7) zRe?ptaCsFsoAvZ@T>xp|7HJJov1@K0a|~!J_WleOlJx<&Sy<(tKm*FCq5gHMR`E_cgbCORH&^02X2m;EZVH`ms#OC%<+dIKArnXG4H!Xp3C=)^@-AA|HPBQ+Y9;%Cw(>)2~dm zbY`?Cg!d->&ZQ3W=b$9ux6fhAXI0kJs|f!9`km}|xb;;$Br^(d1P{yN0ve?;XqoD= zEL#dF(eMj1(;S-|E&%#jEoTd0xW5!nA#(ZuNL?Y~+ONP5g!QUV&wal(^Pz3&2~FXy zC2h@NwT8!J6WHi+IaElw?ls_64nJUOv4X2&d_rz3BBzWif+Bw-(~EEvzf)a>f!|}0 z9;N$hXm!ygdgW6MdlE!9uWE8iLp8*eucWX|VhQ!#+e202&^SKKHUi^*mG zBgU%upafse$uG#z5-Ow@<;D^!(p~}&uJNzs%Z3t4pg+r|672D>@?{Bp`4tuC>{3(; z%HL#kDT41NnNvzm?NtoR(aXc~SSfB)o8`$;D$%x}+sV_n#@u`>TBwgS`E-r`ulR#l z>EC5iko?*I04+mpt*zTfNc=(QQ+osjc&Uf7(%&*9cF&{^(@aYb(HUd z>k3mG0H`~qd?@7`-b1uN?f#CT00_Il8jH)@*PBxRzqq{H%#$|QtE+T7ejl7^YG*Kv zP;(DT!|Hvau2)sX)ueksx8YZ>5C!$nR^HOl7D4P%`!M>ETe(ad~4nWoaicB40h=Ny-Q^(@Du30joVFlSY8&w9FfUYu;Dj$x#o= z^5_U8lCS025!mDzv#MCg=@GzB{}Izj^1HtQqjHgf;)nj+suuRGnakqw=zP#R@D_De zI!01v#yO0^U+g7pdR4WK3C_!7BN3v%2cb}vY2@HY#L^3Lek2a>qIgFkNnA3^gi%m` zoJGn|Op_I(D49rBj-r6mfTL51@p7F`Pway%I=i?zWR1Z|CdoBpu%Tq|=_PTwXABTUiae%>sj_JdZWDt+bSl6m7stR?ZDJgYAdx1y zW5JUyRb$C-$guFmTio$FK(NbC$0CwD??Tic z6i+FLnR*&sG#-l`f@Zp2Qc|jm8;WLtL%V&lX*`(?Wfp$mUZ1>?N-5efkn;KBnG89a zs}!TcK+GE_ zB@+>ljy2t7P6pIQA5|eIgZWQ^A`HRP2>EC*nF(BOgqJe_SgUasQ@i;+aIbfm-mW zICnX1O^HSqwuk$ZR~;0-H(-p_kY~t)lgOFgh%vax^%w1%loia9zfA&kxehLL$|jd4 zk;yPSW-IG|o0!;-YOlx0EKx-Z>i|%{(W1Uout~HWNbn}*X?dtgK2;9zcC&?(A97l1 zBAv0z?k#evoJP`I$(sy^2q|HGPoHK>KUAEr7MyL=FRslo%l+eI+=^Pj?co)TZaFrY z%Yip0rU)~e$R_F2q`CSFPuwDLHf&AAYCoAA?5lO715xyu!D|^;I6biTK z1=S!78ii8y#&t@ll16Ha!Bv1~dZ+xU60x*P-mIiNLytwj&9;OTXD?VBIceheu}4V7)7_UOXtZQVV=#&Af#a@}W9 zC0+T^6ySmT<(E?^;Q2}4#?(ov*QbFmb$$whOG3k7M#rWg&OadEO@WO+D6XkUX%ERY zQ?c-s5}FFo^Dub*`pX}`RcDpQrlMrR^3+tszeki99y-=8pH3y4L+K{=oHc+cYw<=z zQa+7JrWrt090L|q+ZF+}yL@5vDs-z`#+AuIuzQ=>d9$s3b!w4YU}814_oEIMldJD z$>Rw2tLrJgLg`-prxuH;g1QD({?WrCq9s18QzfLXU}b1|w_1>bPkM6d+e3;3>u`Rl zvabflJ6L8kB1)^xI#fB!tpdh>?L!Yh-Rfl}ps!Dk)+3B($hK+Zj8}K9ZoA|)B3U|Q z#|$dNF0-zMMl$6a=%>VmacX%g235^0?^}Z#J!)P3yHIP*ierDLT{hGJXLzKp4oK81 z<=4Wce6qa}vDYtk4OnvMX_5|TwME59ktKI3<{-7>Eq4m+|8nA=D?TkU6 zN3G3m*Ntr~+A32jf2fG|MtZK6&RT4AoJ6LfayuR*muyw2#MMZamTS<~RTxvOZif@l zrxrf2oqmKp5uGZt{nv_bKoPQg5_&aYLh3^C-@NRX{Q!=ldQXe@Tok}@sv35PoT&YHU$r0{qYp#}q^ zLfze+z%oJgC0=)faxD+dkT+^6Ygi-bY9if*b;Q$mI!)-*HyuWSF{WzWNM?$o1{OF= JA`O)L{{Ztg*U map { next -> ExternalShareItemStatus in - switch next { - case .progress: - return .progress - case let .done(data): - let fileName: String - if let value = file.fileName { - fileName = value - } else if file.isVideo { - fileName = "telegram_video.mp4" - } else { - fileName = "file" - } - let randomDirectory = UUID() - let safeFileName = fileName.replacingOccurrences(of: "/", with: "_") - let fileDirectory = NSTemporaryDirectory() + "\(randomDirectory)" - let _ = try? FileManager.default.createDirectory(at: URL(fileURLWithPath: fileDirectory), withIntermediateDirectories: true, attributes: nil) - let filePath = fileDirectory + "/\(safeFileName)" - if let _ = try? FileManager.default.copyItem(at: URL(fileURLWithPath: data.path), to: URL(fileURLWithPath: filePath)) { - return .done(.file(URL(fileURLWithPath: filePath), fileName, file.mimeType)) - } else { - return .progress - } - } + |> mapToSignal { next -> Signal in + switch next { + case .progress: + return .single(.progress) + case let .done(data): + if file.isSticker, let dimensions = file.dimensions { + return chatMessageSticker(postbox: postbox, file: file, small: false, fetched: true, onlyFullSize: true) + |> map { f -> ExternalShareItemStatus in + let context = f(TransformImageArguments(corners: ImageCorners(), imageSize: dimensions, boundingSize: dimensions, intrinsicInsets: UIEdgeInsets(), emptyColor: nil, scale: 1.0)) + if let image = context?.generateImage() { + return .done(.image(image)) + } else { + return .progress + } + } + } else { + let fileName: String + if let value = file.fileName { + fileName = value + } else if file.isVideo { + fileName = "telegram_video.mp4" + } else { + fileName = "file" + } + let randomDirectory = UUID() + let safeFileName = fileName.replacingOccurrences(of: "/", with: "_") + let fileDirectory = NSTemporaryDirectory() + "\(randomDirectory)" + let _ = try? FileManager.default.createDirectory(at: URL(fileURLWithPath: fileDirectory), withIntermediateDirectories: true, attributes: nil) + let filePath = fileDirectory + "/\(safeFileName)" + if let _ = try? FileManager.default.copyItem(at: URL(fileURLWithPath: data.path), to: URL(fileURLWithPath: filePath)) { + return .single(.done(.file(URL(fileURLWithPath: filePath), fileName, file.mimeType))) + } else { + return .single(.progress) + } + } + } }) } else if let mediaReference = item.mediaReference, let image = mediaReference.media as? TelegramMediaImage, let largest = largestImageRepresentation(image.representations) { signals.append(collectExternalShareResource(postbox: postbox, resourceReference: mediaReference.resourceReference(largest.resource), statsCategory: .image) diff --git a/TelegramUI/StickerPackPreviewControllerNode.swift b/TelegramUI/StickerPackPreviewControllerNode.swift index 938eade34a..355c6f88f6 100644 --- a/TelegramUI/StickerPackPreviewControllerNode.swift +++ b/TelegramUI/StickerPackPreviewControllerNode.swift @@ -189,23 +189,25 @@ final class StickerPackPreviewControllerNode: ViewControllerTracingNode, UIScrol |> map { isStarred -> (ASDisplayNode, PeekControllerContent)? in if let strongSelf = self { var menuItems: [PeekControllerMenuItem] = [] - if strongSelf.sendSticker != nil { - menuItems.append(PeekControllerMenuItem(title: strongSelf.presentationData.strings.ShareMenu_Send, color: .accent, font: .bold, action: { - if let strongSelf = self { - strongSelf.sendSticker?(.standalone(media: item.file)) - } - })) - } - menuItems.append(PeekControllerMenuItem(title: isStarred ? strongSelf.presentationData.strings.Stickers_RemoveFromFavorites : strongSelf.presentationData.strings.Stickers_AddToFavorites, color: isStarred ? .destructive : .accent, action: { - if let strongSelf = self { - if isStarred { - let _ = removeSavedSticker(postbox: strongSelf.account.postbox, mediaId: item.file.fileId).start() - } else { - let _ = addSavedSticker(postbox: strongSelf.account.postbox, network: strongSelf.account.network, file: item.file).start() + if let stickerPack = strongSelf.stickerPack, case let .result(info, _, _) = stickerPack, info.id.namespace == Namespaces.ItemCollection.CloudStickerPacks { + if strongSelf.sendSticker != nil { + menuItems.append(PeekControllerMenuItem(title: strongSelf.presentationData.strings.ShareMenu_Send, color: .accent, font: .bold, action: { + if let strongSelf = self { + strongSelf.sendSticker?(.standalone(media: item.file)) } - } - })) - menuItems.append(PeekControllerMenuItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, action: {})) + })) + } + menuItems.append(PeekControllerMenuItem(title: isStarred ? strongSelf.presentationData.strings.Stickers_RemoveFromFavorites : strongSelf.presentationData.strings.Stickers_AddToFavorites, color: isStarred ? .destructive : .accent, action: { + if let strongSelf = self { + if isStarred { + let _ = removeSavedSticker(postbox: strongSelf.account.postbox, mediaId: item.file.fileId).start() + } else { + let _ = addSavedSticker(postbox: strongSelf.account.postbox, network: strongSelf.account.network, file: item.file).start() + } + } + })) + menuItems.append(PeekControllerMenuItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, action: {})) + } return (itemNode, StickerPreviewPeekContent(account: strongSelf.account, item: .pack(item), menu: menuItems)) } else { return nil diff --git a/TelegramUI/StickerResources.swift b/TelegramUI/StickerResources.swift index 4ae81799c6..27dc9a278d 100644 --- a/TelegramUI/StickerResources.swift +++ b/TelegramUI/StickerResources.swift @@ -44,11 +44,11 @@ func chatMessageStickerResource(file: TelegramMediaFile, small: Bool) -> MediaRe return resource } -private func chatMessageStickerDatas(account: Account, file: TelegramMediaFile, small: Bool, fetched: Bool, onlyFullSize: Bool) -> Signal<(Data?, Data?, Bool), NoError> { +private func chatMessageStickerDatas(postbox: Postbox, file: TelegramMediaFile, small: Bool, fetched: Bool, onlyFullSize: Bool) -> Signal<(Data?, Data?, Bool), NoError> { let thumbnailResource = chatMessageStickerResource(file: file, small: true) let resource = chatMessageStickerResource(file: file, small: small) - let maybeFetched = account.postbox.mediaBox.cachedResourceRepresentation(resource, representation: CachedStickerAJpegRepresentation(size: small ? CGSize(width: 160.0, height: 160.0) : nil), complete: false, fetch: false) + let maybeFetched = postbox.mediaBox.cachedResourceRepresentation(resource, representation: CachedStickerAJpegRepresentation(size: small ? CGSize(width: 160.0, height: 160.0) : nil), complete: false, fetch: false) return maybeFetched |> take(1) @@ -58,8 +58,8 @@ private func chatMessageStickerDatas(account: Account, file: TelegramMediaFile, return .single((nil, loadedData, true)) } else { - let thumbnailData = account.postbox.mediaBox.cachedResourceRepresentation(thumbnailResource, representation: CachedStickerAJpegRepresentation(size: nil), complete: false) - let fullSizeData = account.postbox.mediaBox.cachedResourceRepresentation(resource, representation: CachedStickerAJpegRepresentation(size: small ? CGSize(width: 160.0, height: 160.0) : nil), complete: onlyFullSize) + let thumbnailData = postbox.mediaBox.cachedResourceRepresentation(thumbnailResource, representation: CachedStickerAJpegRepresentation(size: nil), complete: false) + let fullSizeData = postbox.mediaBox.cachedResourceRepresentation(resource, representation: CachedStickerAJpegRepresentation(size: small ? CGSize(width: 160.0, height: 160.0) : nil), complete: onlyFullSize) |> map { next in return (next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: .mappedIfSafe), next.complete) } @@ -67,12 +67,12 @@ private func chatMessageStickerDatas(account: Account, file: TelegramMediaFile, return Signal { subscriber in var fetch: Disposable? if fetched { - fetch = fetchedMediaResource(postbox: account.postbox, reference: stickerPackFileReference(file).resourceReference(resource)).start() + fetch = fetchedMediaResource(postbox: postbox, reference: stickerPackFileReference(file).resourceReference(resource)).start() } var fetchThumbnail: Disposable? if !thumbnailResource.id.isEqual(to: resource.id) { - fetchThumbnail = fetchedMediaResource(postbox: account.postbox, reference: stickerPackFileReference(file).resourceReference(thumbnailResource)).start() + fetchThumbnail = fetchedMediaResource(postbox: postbox, reference: stickerPackFileReference(file).resourceReference(thumbnailResource)).start() } let disposable = (combineLatest(thumbnailData, fullSizeData) |> map { thumbnailData, fullSizeData -> (Data?, Data?, Bool) in @@ -96,7 +96,7 @@ private func chatMessageStickerDatas(account: Account, file: TelegramMediaFile, } func chatMessageLegacySticker(account: Account, file: TelegramMediaFile, small: Bool, fitSize: CGSize, fetched: Bool = false, onlyFullSize: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { - let signal = chatMessageStickerDatas(account: account, file: file, small: small, fetched: fetched, onlyFullSize: onlyFullSize) + let signal = chatMessageStickerDatas(postbox: account.postbox, file: file, small: small, fetched: fetched, onlyFullSize: onlyFullSize) return signal |> map { (thumbnailData, fullSizeData, fullSizeComplete) in return { preArguments in var fullSizeImage: (UIImage, UIImage)? @@ -160,7 +160,11 @@ func chatMessageLegacySticker(account: Account, file: TelegramMediaFile, small: } public func chatMessageSticker(account: Account, file: TelegramMediaFile, small: Bool, fetched: Bool = false, onlyFullSize: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { - let signal = chatMessageStickerDatas(account: account, file: file, small: small, fetched: fetched, onlyFullSize: onlyFullSize) + return chatMessageSticker(postbox: account.postbox, file: file, small: small, fetched: fetched, onlyFullSize: onlyFullSize) +} + +public func chatMessageSticker(postbox: Postbox, file: TelegramMediaFile, small: Bool, fetched: Bool = false, onlyFullSize: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { + let signal = chatMessageStickerDatas(postbox: postbox, file: file, small: small, fetched: fetched, onlyFullSize: onlyFullSize) return signal |> map { (thumbnailData, fullSizeData, fullSizeComplete) in return { arguments in diff --git a/TelegramUI/ThemeSettingsChatPreviewItem.swift b/TelegramUI/ThemeSettingsChatPreviewItem.swift index 601fed7edd..1927dba543 100644 --- a/TelegramUI/ThemeSettingsChatPreviewItem.swift +++ b/TelegramUI/ThemeSettingsChatPreviewItem.swift @@ -99,6 +99,7 @@ class ThemeSettingsChatPreviewItemNode: ListViewItemNode { return false }, navigateToFirstDateMessage: { _ in }, requestRedeliveryOfFailedMessages: { _ in + }, addContact: { _ in }, requestMessageUpdate: { _ in }, cancelInteractiveKeyboardGestures: { }, automaticMediaDownloadSettings: AutomaticMediaDownloadSettings.defaultSettings) diff --git a/TelegramUI/TimestampStrings.swift b/TelegramUI/TimestampStrings.swift deleted file mode 100644 index 9a30829567..0000000000 --- a/TelegramUI/TimestampStrings.swift +++ /dev/null @@ -1,3 +0,0 @@ -import Foundation - - diff --git a/TelegramUI/UIImage+WebP.h b/TelegramUI/UIImage+WebP.h index 56e92783ca..dfab6efbe4 100644 --- a/TelegramUI/UIImage+WebP.h +++ b/TelegramUI/UIImage+WebP.h @@ -3,5 +3,6 @@ @interface UIImage (WebP) + (UIImage *)convertFromWebP:(NSData *)data; ++ (NSData *)convertToWebP:(UIImage *)image quality:(CGFloat)quality error:(NSError **)error; @end diff --git a/TelegramUI/UIImage+WebP.m b/TelegramUI/UIImage+WebP.m index c8a671e25d..a9e1812fce 100644 --- a/TelegramUI/UIImage+WebP.m +++ b/TelegramUI/UIImage+WebP.m @@ -75,4 +75,76 @@ return image; } ++ (NSData *)convertToWebP:(UIImage *)image quality:(CGFloat)quality error:(NSError **)error { + WebPPreset preset = WEBP_PRESET_DEFAULT; + CGImageRef webPImageRef = image.CGImage; + size_t webPBytesPerRow = CGImageGetBytesPerRow(webPImageRef); + + size_t webPImageWidth = CGImageGetWidth(webPImageRef); + size_t webPImageHeight = CGImageGetHeight(webPImageRef); + + CGDataProviderRef webPDataProviderRef = CGImageGetDataProvider(webPImageRef); + CFDataRef webPImageDatRef = CGDataProviderCopyData(webPDataProviderRef); + + uint8_t *webPImageData = (uint8_t *)CFDataGetBytePtr(webPImageDatRef); + + WebPConfig config; + if (!WebPConfigPreset(&config, preset, quality)) { + NSMutableDictionary *errorDetail = [NSMutableDictionary dictionary]; + [errorDetail setValue:@"Configuration preset failed to initialize." forKey:NSLocalizedDescriptionKey]; + if(error != NULL) + *error = [NSError errorWithDomain:[NSString stringWithFormat:@"%@.errorDomain", [[NSBundle mainBundle] bundleIdentifier]] code:-101 userInfo:errorDetail]; + + CFRelease(webPImageDatRef); + return nil; + } + + config.method = 6; + //if (configBlock) { + // configBlock(&config); + //} + + if (!WebPValidateConfig(&config)) { + NSMutableDictionary *errorDetail = [NSMutableDictionary dictionary]; + [errorDetail setValue:@"One or more configuration parameters are beyond their valid ranges." forKey:NSLocalizedDescriptionKey]; + if(error != NULL) + *error = [NSError errorWithDomain:[NSString stringWithFormat:@"%@.errorDomain", [[NSBundle mainBundle] bundleIdentifier]] code:-101 userInfo:errorDetail]; + + CFRelease(webPImageDatRef); + return nil; + } + + WebPPicture pic; + if (!WebPPictureInit(&pic)) { + NSMutableDictionary *errorDetail = [NSMutableDictionary dictionary]; + [errorDetail setValue:@"Failed to initialize structure. Version mismatch." forKey:NSLocalizedDescriptionKey]; + if(error != NULL) + *error = [NSError errorWithDomain:[NSString stringWithFormat:@"%@.errorDomain", [[NSBundle mainBundle] bundleIdentifier]] code:-101 userInfo:errorDetail]; + + CFRelease(webPImageDatRef); + return nil; + } + pic.width = (int)webPImageWidth; + pic.height = (int)webPImageHeight; + pic.colorspace = WEBP_YUV420; + + WebPPictureImportRGBA(&pic, webPImageData, (int)webPBytesPerRow); + WebPPictureARGBToYUVA(&pic, WEBP_YUV420); + WebPCleanupTransparentArea(&pic); + + WebPMemoryWriter writer; + WebPMemoryWriterInit(&writer); + pic.writer = WebPMemoryWrite; + pic.custom_ptr = &writer; + WebPEncode(&config, &pic); + + NSData *webPFinalData = [NSData dataWithBytes:writer.mem length:writer.size]; + + free(writer.mem); + WebPPictureFree(&pic); + CFRelease(webPImageDatRef); + + return webPFinalData; +} + @end diff --git a/TelegramUI/UniversalVideoGalleryItem.swift b/TelegramUI/UniversalVideoGalleryItem.swift index 8996243f30..66a751b0e7 100644 --- a/TelegramUI/UniversalVideoGalleryItem.swift +++ b/TelegramUI/UniversalVideoGalleryItem.swift @@ -19,12 +19,13 @@ class UniversalVideoGalleryItem: GalleryItem { let indexData: GalleryItemIndexData? let contentInfo: UniversalVideoGalleryItemContentInfo? let caption: NSAttributedString + let credit: 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: NSAttributedString, hideControls: Bool = false, playbackCompleted: @escaping () -> Void = {}, openUrl: @escaping (String) -> Void, openUrlOptions: @escaping (String) -> Void) { + init(account: Account, presentationData: PresentationData, content: UniversalVideoContent, originData: GalleryItemOriginData?, indexData: GalleryItemIndexData?, contentInfo: UniversalVideoGalleryItemContentInfo?, caption: NSAttributedString, credit: NSAttributedString? = nil, hideControls: Bool = false, playbackCompleted: @escaping () -> Void = {}, openUrl: @escaping (String) -> Void, openUrlOptions: @escaping (String) -> Void) { self.account = account self.presentationData = presentationData self.content = content @@ -32,6 +33,7 @@ class UniversalVideoGalleryItem: GalleryItem { self.indexData = indexData self.contentInfo = contentInfo self.caption = caption + self.credit = credit self.hideControls = hideControls self.playbackCompleted = playbackCompleted self.openUrl = openUrl @@ -314,7 +316,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { self.scrubberView.setBufferingStatusSignal(videoNode.bufferingStatus) - var requiresDownload = true + self.requiresDownload = true var mediaFileStatus: Signal = .single(nil) if let contentInfo = item.contentInfo, case let .message(message) = contentInfo { var file: TelegramMediaFile? @@ -426,7 +428,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { else if isPaused { if hasStarted || strongSelf.didPause || buffering { strongSelf.footerContentNode.content = .playback(paused: true, seekable: seekable) - } else if let fetchStatus = fetchStatus, !requiresDownload { + } else if let fetchStatus = fetchStatus, !strongSelf.requiresDownload { strongSelf.footerContentNode.content = .fetch(status: fetchStatus) } } else { diff --git a/TelegramUI/WebP.swift b/TelegramUI/WebP.swift index 9a30829567..f8f4d2859d 100644 --- a/TelegramUI/WebP.swift +++ b/TelegramUI/WebP.swift @@ -1,3 +1,32 @@ -import Foundation +import UIKit +import SwiftSignalKit +import LegacyComponents +private func scaleImage(_ image: UIImage, dimensions: CGSize) -> UIImage? { + if #available(iOSApplicationExtension 10.0, *) { + let format = UIGraphicsImageRendererFormat() + format.scale = 1.0 + let renderer = UIGraphicsImageRenderer(size: dimensions, format: format) + return renderer.image { _ in + image.draw(in: CGRect(origin: .zero, size: dimensions)) + } + } else { + return TGScaleImageToPixelSize(image, dimensions) + } +} +func convertToWebP(image: UIImage, targetSize: CGSize?, quality: CGFloat) -> Signal { + var image = image + if let targetSize = targetSize, let scaledImage = scaleImage(image, dimensions: targetSize) { + image = scaledImage + } + + return Signal { subscriber in + if let data = try? UIImage.convert(toWebP: image, quality: quality * 100.0) { + subscriber.putNext(data) + } + subscriber.putCompletion() + + return EmptyDisposable + } |> runOn(Queue.concurrentDefaultQueue()) +}